如何在getter链之后安全调用setter,例如foo.getX().getY().setZ(...);
?例如,假设我有一个嵌套的POJO,并且希望能够设置一个嵌套对象的字段。
Foo foo = ...
foo.getX().getY().setZ(...);
我希望行为是这样,如果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(...);
我想知道是否有一个接近这个的使用反射/功能的技巧。
我也有以下限制:
x = foo.getX()
修改foo。 答案 0 :(得分:6)
使用函数式编程。创建一个接受默认值的getter,setter和provider的方法,该方法将返回封装您所需逻辑的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;
};
}
然后创建这些装饰的吸气剂:
Function<Foo, X> getX = getOrSetDefault(Foo::getX, Foo::setX, X::new);
Function<X, Y> getY = getOrSetDefault(X::getY, X::setY, Y::new);
最后,将它们链接起来并应用在您的foo
实例中传递的结果函数作为参数:
Foo foo = ...
getX.andThen(getY).apply(foo).setZ(...);
编辑:这假设X
和Y
都具有一个分别由X::new
和Y::new
引用的无参数构造函数。但是您可以使用任何东西作为Supplier
,即已经创建的实例,或方法的返回值,等等。
答案 1 :(得分:3)
TL; DR:不要试图在显然没有地方的地方强制使用Java。
在Java 8 中无需修改任何类即可在功能上实现此目的的唯一方法是使用i
及其Optional
方法。它真的很长很长,但是很快,但这是只有在一行中才使用功能才有意义的唯一方法。
.orElse()
如果Optional.ofNullable(foo.getX()).orElseGet(() -> { foo.setX(new X()); return foo.getX(); }).setY(...);
也返回设置的值,则可以简化为:
foo.setX()
这是我能想到的唯一通用和实用的方法。综上所述,您可以清楚地看到,即使只是两个吸气剂链,这种情况也变得庞大而丑陋,所以我不建议这样做。如果您必须链接多个呼叫,我绝对建议您使用经典的多语句方法。
另一个选项,甚至认为不是真正的功能,也可以使用三态运算符,仅当设置器返回设置的值时仍然使用 :
Optional.ofNullable(foo.getX()).orElseGet(() -> foo.setX(new X())).setY(...);
如果找到了元素,这可能会带来不必要的副作用,即两次调用getter,您可能不喜欢这种副作用,但是如果getter以某种方式缓存了值,则可能会被忽略。
答案 2 :(得分:2)
首先,我只想提一下这可能不是最佳解决方案,而且我敢肯定有优化此方法的方法。也就是说,我想再次尝试使用CGLIB和ObjenesisHelper。
使用CGLIB和ObjenesisHelper,我们可以将数据对象包装在一个代理中,该代理将拦截get
方法。使用此拦截器,我们可以添加您在帖子中描述的逻辑。让我们首先假设这些是我们的数据类型(为简便起见,请使用lombok)。
@Data class W { private X x; }
@Data class X { private Y y; }
@Data class Y { private Z z; }
@Data class Z { private int alpha; }
我们的最终解决方案可以如下使用:
public static void main(String[] args) {
final W w = ProxyUtil.withLazyDefaults(new W());
System.out.println(w.getX().getY().getZ().getAlpha());
}
当前,如果我们尝试调用new W().getX().getY().getZ().getAlpha()
,则在调用NullPointerException
时会得到一个getY()
,因为getX()
返回了null。即使我们设法产生一个默认的X
值,我们仍将需要一个默认的Y
值才能在getZ()
和getAlpha()
上不获取空指针,依此类推。我们创建的代理需要通用,并且能够递归包装其子组件。
好的,让我们开始吧。我们需要做的第一件事是创建一个MethodInterceptor
。每当任何呼叫到达我们的代理实例时,它将执行MethodInterceptor
的逻辑。我们首先需要确定所调用的方法是否是吸气剂。如果没有,我们将忽略它。在此getter调用期间,如果在我们的数据中不存在该值,我们将创建它并更新该对象。如果getter包含的值是原始的未包装类,我们将用包装的版本替换它。最后,我们将返回包装的实例。 编辑,我对此进行了更新,以不将包装实例插入到实际的Data对象中。如果以这种方式多次访问对象,则性能会降低
public class ProxyUtil {
public static <T> T withLazyDefaults(final T data) {
final MethodInterceptor interceptor = (object, method, args, proxy) -> {
if (method.getName().startsWith("get")) {
final Class<?> returnType = method.getReturnType();
Object response = method.invoke(data, args);
if (response == null) {
response = returnType.newInstance();
data.getClass()
.getDeclaredMethod(
method.getName().replaceFirst("get", "set"),
returnType)
.invoke(data, response);
}
if (!returnType.isPrimitive()) {
response = withLazyDefaults(response);
}
return response;
}
return method.invoke(data, args);
};
...
此方法的其余部分涉及使用CGLIB和Objenisis Helper构造包装实例。 CGLib将允许您代理类和接口,而ObjenesisHelper将允许您构造类的实例而无需调用构造函数。参见here for a CGLib example和here for a ObjenesisHelper example。
...
final Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(data.getClass());
final Set<Class<?>> interfaces = new LinkedHashSet<>();
if (data.getClass().isInterface()) {
interfaces.add(data.getClass());
}
interfaces.addAll(Arrays.asList(data.getClass().getInterfaces()));
enhancer.setInterfaces(interfaces.toArray(new Class[interfaces.size()]));
enhancer.setCallbackType(interceptor.getClass());
final Class<?> proxyClass = enhancer.createClass();
Enhancer.registerStaticCallbacks(proxyClass, new Callback[]{interceptor});
return (T) ObjenesisHelper.newInstance(proxyClass);
}
}
答案 3 :(得分:1)
我最终结合使用了功能和反射,并试图使该接口类似于Java的Optional。这是我写foo.getX().getY().setZ(val);
MutableGetter.of(foo).map(Foo::getX).map(x::getY).get().setZ(val);
这是代码(仍为WIP)。 我使用反射来避免必须传递setter和构造函数
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Supplier;
import javassist.util.proxy.MethodHandler;
import javassist.util.proxy.ProxyFactory;
import lombok.Getter;
import lombok.NonNull;
public class MutableGetter<T>
{
private T object;
private MutableGetter(T object)
{
this.object = object;
}
public static <T> MutableGetter<T> of(@NonNull T object)
{
return new MutableGetter<>(object);
}
public <U> MutableGetter<U> map(Function<T, U> getter)
{
Method getterMethod = getGetterMethod(object.getClass(), getter);
BiConsumer<T, U> setter = getSetter(getterMethod);
Supplier<U> defaultValue = getDefaultValue(getterMethod);
U nextObject = getter.apply(object);
if (nextObject == null) {
nextObject = defaultValue.get();
setter.accept(object, nextObject);
}
return new MutableGetter<>(nextObject);
}
public T get()
{
return object;
}
private static <U> Supplier<U> getDefaultValue(Method getterMethod)
{
return () -> {
try {
Constructor<?> constructor = getterMethod.getReturnType().getConstructor();
constructor.setAccessible(true);
return (U) constructor.newInstance();
} catch (Exception e) {
throw new IllegalStateException(e);
}
};
}
private static <T, U> BiConsumer<T,U> getSetter(Method getterMethod)
{
return (obj, arg) -> {
Method setterMethod = getSetterFromGetter(getterMethod);
setterMethod.setAccessible(true);
try {
setterMethod.invoke(obj, arg);
} catch (Exception e) {
throw new IllegalStateException(e);
}
};
}
private static Method getSetterFromGetter(Method getter)
{
if (!getter.getName().startsWith("get")) {
throw new IllegalStateException("The getter method must start with 'get'");
}
String setterName = getter.getName().replaceFirst("get", "set");
Method[] methods = getter.getDeclaringClass().getMethods();
for (Method method: methods) {
if (method.getName().equals(setterName)) {
return method;
}
}
throw new IllegalStateException(String.format("Couldn't find setter in class %s with name %s", getter.getDeclaringClass(), setterName));
}
private static <T, U> Method getGetterMethod(Class<?> clazz, Function<T, U> getter)
{
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setSuperclass(clazz);
MethodRecorder methodRecorder = new MethodRecorder();
T proxy;
try {
proxy = (T) proxyFactory.create(new Class<?>[0], new Object[0], methodRecorder);
} catch (Exception e) {
throw new IllegalStateException(e);
}
getter.apply(proxy);
return methodRecorder.getLastInvokedMethod();
}
private static class MethodRecorder implements MethodHandler
{
@Getter
private Method lastInvokedMethod;
@Override
public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args)
{
this.lastInvokedMethod = thisMethod;
return null; // the result is ignored
}
}
}
如果您有任何建议,请告诉我