我试图通过LambdaMetafactory动态地创建一个BiConsumer类型的方法引用。https:/www.cuba-platform.comblogthink-twice-before-using-reflection - createVoidHandlerLambda和这里的 将BiConsumer创建为无反射的Field setter。 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");
}
}
我将感激你对我的帮助。
EDIT #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的调用。
请注意,您使用的 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
,以最终调用目标方法。
我们可以显式地指定类型,但为了使代码尽可能地可重用,我们可以调用 type()
上,然后使用适配器方法。
wrap()
会将所有基元类型转换为它们的封装类型。不幸的是,这也意味着将返回类型转换为 Void
所以我们必须把它退到 void
再次。这就给了我们 实例方法类型 参数。(与 文件)erase()
将把所有引用类型转换为 Object
但让所有基元类型保持原样。所以将其应用于 实例方法类型 给了我们被擦除的类型。这取决于特定的目标接口,这种简单的转换是否足够。对于 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();
}