在getter链之后安全地调用setter,例如foo.getX().getY().setZ(...);

Dis*_*ame 9 java reflection functional-programming java-8

如何在getter链之后安全地调用setter,例如foo.getX().getY().setZ(...);?例如,假设我有一个嵌套的POJO,我希望能够设置嵌套对象的字段.

Foo foo = ...
foo.getX().getY().setZ(...);
Run Code Online (Sandbox Code Playgroud)

我希望行为是这样的,如果X和Y不存在,那么它们是自动创建的; 否则它会重用现有对象.

换句话说,我希望它的行为相当于

Foo foo = ...
X x = foo.getX();
if (x == null) { 
  x = new X();
  foo.setX(x);
}

Y y = x.getY();
if (y == null) {
  y = newY();
  x.setY(y);
}

y.setZ(...);
Run Code Online (Sandbox Code Playgroud)

我想知道是否有一个使用反射/功能的技巧接近这一点.

我也有以下约束:

  • 我无法修改任何类
  • 解决方案必须只知道公共getter和setter,而不是私有实例变量
  • 我希望getter仅在特别请求时修改内部状态; 我不想x = foo.getX()修改foo.

Fed*_*ner 6

使用函数式编程.创建一个接受getter,setter和默认值供应商的方法,该方法返回一个封装所需逻辑的getter:

public static <T, U> Function<T, U> getOrSetDefault(
        Function<T, U> getter,
        BiConsumer<T, U> setter,
        Supplier<U> defaultValue) {

    return t -> {
        U u = getter.apply(t);
        if (u == null) {
            u = defaultValue.get();
            setter.accept(t, u);
        }
        return u;
    };
}
Run Code Online (Sandbox Code Playgroud)

然后创建这些装饰的 getter:

Function<Foo, X> getX = getOrSetDefault(Foo::getX, Foo::setX, X::new);
Function<X, Y> getY = getOrSetDefault(X::getY, X::setY, Y::new);
Run Code Online (Sandbox Code Playgroud)

最后,链接它们并将生成的函数foo作为参数应用于实例中:

Foo foo = ...
getX.andThen(getY).apply(foo).setZ(...);
Run Code Online (Sandbox Code Playgroud)

编辑:这假定两者XY具有由所引用的无参数的构造函数X::newY::new分别.但是你可以使用任何东西作为Supplier,即已经创建的实例,或方法的返回值等.

  • 请注意,这里没有任何内容假定缺少对象的默认值是no-args构造函数返回的(或者这样的构造函数存在).您可以轻松地将"X :: new"以外的内容插入到访问者定义中. (2认同)

Mar*_*lli 3

TL;DR:不要试图在显然没有地方使用函数式 Java 的地方。


在 Java 8 中,无需修改任何类即可实现此功能的唯一方法是使用Optionals 及其.orElse()方法。它变得非常长非常快,但如果您只想在一行中完成它,那么这是使用函数实际上有意义的唯一方法。

Optional.ofNullable(foo.getX()).orElseGet(() -> { foo.setX(new X()); return foo.getX(); }).setY(...);
Run Code Online (Sandbox Code Playgroud)

如果foo.setX()也返回设定值则可以简化为:

Optional.ofNullable(foo.getX()).orElseGet(() -> foo.setX(new X())).setY(...);
Run Code Online (Sandbox Code Playgroud)

这是我能想到的唯一通用且实用的方法。如上所述,您可以清楚地看到,即使对于两个吸气剂链,这也会变得巨大且丑陋,所以我不建议这样做。如果您必须链接多个调用,我绝对建议您使用经典的多语句方法。

另一种选择,即使认为功能不太强大,也是使用三态运算符,但前提是设置器返回设置的值

(foo.getX() == null ? foo.setX(new X()) : foo.getX()).setY(...);
Run Code Online (Sandbox Code Playgroud)

如果找到该元素,这可能会产生不必要的副作用,即调用 getter 两次,您可能不喜欢这种副作用,但如果 getter 以某种方式缓存该值,则可能会被忽略。