[英]Create BiConsumer from LambdaMetafactory
I'm trying to dynamically create a method reference of type BiConsumer through LambdaMetafactory.我正在尝试通过 LambdaMetafactory 动态创建 BiConsumer 类型的方法引用。 I was trying to apply two approaches found on https://www.cuba-platform.com/blog/think-twice-before-using-reflection/ - createVoidHandlerLambda and here Create BiConsumer as Field setter without reflection the Holger's answer.我试图应用在https://www.cuba-platform.com/blog/think-twice-before-using-reflection/ - createVoidHandlerLambda 和此处Create BiConsumer as Field setter 上找到的两种方法,而不反映Holger 的答案。
However in both cases I'm having below error:但是,在这两种情况下,我都有以下错误:
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)
My code is something like this:我的代码是这样的:
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);
}
}
Where MyClass looks like this: MyClass 看起来像这样:
public class MyClass {
public boolean valid;
public void setValid(boolean valid) {
this.valid = valid;
System.out.println("Called setValid");
}
}
I will appreciate for help with this one.我将感谢您对此的帮助。
EDIT #1.编辑#1。 After consulting @Holger I've modified createSetter method to:在咨询@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);
}
Now this method does not throw the initial Exception althoug it seems that calling accept on this method reference has no effect.现在这个方法不会抛出初始异常,尽管在这个方法引用上调用 accept 似乎没有效果。 I do not see "Called setValid" in logs for this call.我在此调用的日志中没有看到“Called setValid”。 Only for MyClass::setValid;仅适用于 MyClass::setValid;
Note that your use of getMethod
and caller.findVirtual(…)
for the same method is redundant.请注意,您对同一方法使用getMethod
和caller.findVirtual(…)
是多余的。 If your starting point is a Method
, you may use unreflect
, eg如果您的起点是Method
,您可以使用unreflect
,例如
Method method = classType.getMethod("setValid", boolean.class);
MethodHandles.Lookup caller = MethodHandles.lookup();
MethodHandle target = caller.unreflect(method);
This might be useful when you discover methods dynamically and/or are looking for other artifacts like annotations in the process.当您动态发现方法和/或在过程中寻找其他工件(如注释)时,这可能很有用。 Otherwise, just getting the MethodHandle
via findVirtual
is enough.否则,只需通过findVirtual
MethodHandle
足够了。
Then, you have to understand the three different function types:然后,你要了解三种不同的 function 类型:
(MyClass,boolean) → void
在您的情况下,它是(MyClass,boolean) → void
BiConsumer<MyClass, Boolean>
, which is (MyClass,Boolean) → void
BiConsumer<MyClass, Boolean>
,即(MyClass,Boolean) → void
BiConsumer
interface, which is (Object,Object) → void
BiConsumer
接口的擦除类型,即(Object,Object) → void
Only specifying all three types correctly tells the factory that it must implement the method只有正确指定所有三种类型,才能告诉工厂它必须实现该方法void accept(Object,Object)
with code which will cast the first argument to MyClass
and the second to Boolean
, followed by unwrapping the second argument to boolean
, to eventually invoke the target method. void accept(Object,Object)
的代码将第一个参数转换为MyClass
,第二个转换为Boolean
,然后将第二个参数解包到boolean
,最终调用目标方法。
We could specify the types explicitly, but to make the code as reusable as possible, we can call type()
on the target, followed by using adapter methods.我们可以显式指定类型,但为了使代码尽可能可重用,我们可以在目标上调用type()
,然后使用适配器方法。
wrap()
will convert all primitive types to their wrapper type. wrap()
会将所有原始类型转换为它们的包装器类型。 Unfortunately, this also implies converting the return type to Void
, so we have to set it back to void
again.不幸的是,这也意味着将返回类型转换为Void
,因此我们必须再次将其设置回void
。erase()
will convert all reference types to Object
but leave all primitive types as-is. erase()
会将所有引用类型转换为Object
但保留所有原始类型。 So applying it to the instantiatedMethodType gives us the erased type.所以将它应用到instantiatedMethodType会给我们擦除类型。java.util.function
, it is.对于java.util.function
中的接口,它是。Another point to raise the reusability is to use an actual type parameter for the method receiver class, as we get the class as parameter anyway:提高可重用性的另一点是为方法接收器 class 使用实际类型参数,因为无论如何我们都将 class 作为参数:
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();
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.