我正在尝试通过LambdaMetafactory动态创建BiConsumer类型的方法引用。 我试图应用在https://www.cuba-platform.com/blog/think-twice-before-using-reflection/上找到的两种方法-createVoidHandlerLambda和此处Create BiConsumer as Field setter without reflection的Holger答案。
但是在两种情况下,我都遇到以下错误:
Exception in thread "main" java.lang.AbstractMethodError: Receiver class org.home.ref.App$$Lambda$15/0x0000000800066040 does not define or inherit an implementation of the resolved method abstract accept(Ljava/lang/Object;Ljava/lang/Object;)V of interface java.util.function.BiConsumer.
at org.home.ref.App.main(App.java:20)
我的代码是这样的:
public class App {
public static void main(String[] args) throws Throwable {
MyClass myClass = new MyClass();
BiConsumer<MyClass, Boolean> setValid = MyClass::setValid;
setValid.accept(myClass, true);
BiConsumer<MyClass, Boolean> mappingMethodReferences = createHandlerLambda(MyClass.class);
mappingMethodReferences.accept(myClass, true);
}
@SuppressWarnings("unchecked")
public static BiConsumer<MyClass, Boolean> createHandlerLambda(Class<?> classType) throws Throwable {
Method method = classType.getMethod("setValid", boolean.class);
MethodHandles.Lookup caller = MethodHandles.lookup();
CallSite site = LambdaMetafactory.metafactory(caller,
"accept",
MethodType.methodType(BiConsumer.class),
MethodType.methodType(void.class, MyClass.class, boolean.class),
caller.findVirtual(classType, method.getName(),
MethodType.methodType(void.class, method.getParameterTypes()[0])),
MethodType.methodType(void.class, classType, method.getParameterTypes()[0]));
MethodHandle factory = site.getTarget();
return (BiConsumer<MyClass, Boolean>) factory.invoke();
}
public static <C, V> BiConsumer<C, V> createSetter(Class<?> classType) throws Throwable {
Field field = classType.getDeclaredField("valid");
MethodHandles.Lookup lookup = MethodHandles.lookup();
final MethodHandle setter = lookup.unreflectSetter(field);
final CallSite site = LambdaMetafactory.metafactory(lookup,
"accept", MethodType.methodType(BiConsumer.class, MethodHandle.class),
setter.type().erase(), MethodHandles.exactInvoker(setter.type()), setter.type());
return (BiConsumer<C, V>)site.getTarget().invokeExact(setter);
}
}
MyClass如下所示:
public class MyClass {
public boolean valid;
public void setValid(boolean valid) {
this.valid = valid;
System.out.println("Called setValid");
}
}
我会很乐意为您提供帮助。
编辑#1。 在咨询@Holger之后,我将createSetter方法修改为:
@SuppressWarnings("unchecked")
public static <C, V> BiConsumer<C, V> createSetter(Class<?> classType) throws Throwable {
Field field = classType.getDeclaredField("valid");
MethodHandles.Lookup lookup = MethodHandles.lookup();
final MethodHandle setter = lookup.unreflectSetter(field);
MethodType type = setter.type();
if(field.getType().isPrimitive())
type = type.wrap().changeReturnType(void.class);
final CallSite site = LambdaMetafactory.metafactory(lookup,
"accept", MethodType.methodType(BiConsumer.class, MethodHandle.class),
type.erase(), MethodHandles.exactInvoker(setter.type()), type);
return (BiConsumer<C, V>)site.getTarget().invokeExact(setter);
}
现在,此方法不会引发初始Exception异常,似乎在此方法引用上调用accept无效。我在此呼叫的日志中看不到“ Called setValid”。仅适用于MyClass :: setValid;
答案 0 :(得分:4)
请注意,对同一方法使用getMethod
和caller.findVirtual(…)
是多余的。如果您的起点是Method
,则可以使用unreflect
,例如
Method method = classType.getMethod("setValid", boolean.class);
MethodHandles.Lookup caller = MethodHandles.lookup();
MethodHandle target = caller.unreflect(method);
当您动态发现方法和/或在流程中寻找其他工件(例如注释)时,这可能很有用。否则,仅通过MethodHandle
获得findVirtual
就足够了。
然后,您必须了解三种不同的功能类型:
(MyClass,boolean) → void
BiConsumer<MyClass, Boolean>
,即(MyClass,Boolean) → void
BiConsumer
界面的擦除类型为(Object,Object) → void
仅正确指定所有三种类型会告诉工厂必须实现该方法
void accept(Object,Object)
的代码将第一个参数转换为MyClass
,第二个参数转换为Boolean
,然后将第二个参数解包为boolean
,以最终调用目标方法。 / p>
我们可以显式地指定类型,但是要使代码尽可能地可重用,可以在目标上调用type()
,然后使用适配器方法。
wrap()
将所有原始类型转换为其包装类型。不幸的是,这还意味着将返回类型转换为Void
,因此我们必须再次将其设置回void
。erase()
将所有引用类型转换为Object
,但所有原始类型均保持原样。因此,将其应用于 instantiatedMethodType 可以得到擦除的类型。java.util.function
中的接口,是。提高可重用性的另一点是,对于方法接收器类,使用实际的类型参数,因为无论如何我们都将类作为参数:
public static <T>
BiConsumer<T, Boolean> createHandlerLambda(Class<T> classType) throws Throwable {
MethodHandles.Lookup caller = MethodHandles.lookup();
MethodHandle target = caller.findVirtual(classType, "setValid",
MethodType.methodType(void.class, boolean.class));
MethodType instantiated = target.type().wrap().changeReturnType(void.class);
CallSite site = LambdaMetafactory.metafactory(caller,
"accept", MethodType.methodType(BiConsumer.class),
instantiated.erase(), target, instantiated);
return (BiConsumer<T, Boolean>)site.getTarget().invoke();
}