从 LambdaMetafactory 创建 BiConsumer
Create BiConsumer from LambdaMetafactory
我正在尝试通过 LambdaMetafactory 动态创建 BiConsumer 类型的方法引用。
我试图应用在 https://www.cuba-platform.com/blog/think-twice-before-using-reflection/ - createVoidHandlerLambda and here Holger 的回答中找到的两种方法。
但是在这两种情况下我都遇到了以下错误:
Exception in thread "main" java.lang.AbstractMethodError: Receiver class org.home.ref.App$$Lambda/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);
}
现在此方法不会抛出初始异常,尽管似乎对此方法引用调用 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);
当您动态发现方法时,这可能很有用 and/or 正在寻找过程中的其他工件,如注释。否则,只需通过 findVirtual
获取 MethodHandle
就足够了。
那么,你必须了解三种不同的函数类型:
- 目标方法句柄具有特定类型,在将方法句柄传递给工厂时隐式给出该类型。在你的情况下,它是
(MyClass,boolean) → void
- 与预期结果类型关联的通用函数类型
BiConsumer<MyClass, Boolean>
,即(MyClass,Boolean) → void
BiConsumer
接口的擦除类型,即(Object,Object) → void
只有正确指定所有三种类型才能告诉工厂它必须实现该方法
void accept(Object,Object)
的代码会将第一个参数转换为 MyClass
,将第二个参数转换为 Boolean
,然后将第二个参数解包为 boolean
,最终调用目标方法。
我们可以显式指定类型,但为了使代码尽可能可重用,我们可以在目标上调用 type()
,然后使用适配器方法。
wrap()
会将所有基本类型转换为它们的包装类型。不幸的是,这也意味着将 return 类型转换为 Void
,因此我们必须再次将其设置回 void
。
这为我们提供了 instantiatedMethodType 参数。 (比较the documentation)
erase()
会将所有引用类型转换为 Object
但保留所有原始类型。所以将它应用于 instantiatedMethodType 给我们擦除的类型。
这种简单的转换是否足够取决于特定的目标接口。对于java.util.function
中的接口,它是。
提高可重用性的另一点是为方法接收器使用实际类型参数 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();
}
我正在尝试通过 LambdaMetafactory 动态创建 BiConsumer 类型的方法引用。
我试图应用在 https://www.cuba-platform.com/blog/think-twice-before-using-reflection/ - createVoidHandlerLambda and here
但是在这两种情况下我都遇到了以下错误:
Exception in thread "main" java.lang.AbstractMethodError: Receiver class org.home.ref.App$$Lambda/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);
}
现在此方法不会抛出初始异常,尽管似乎对此方法引用调用 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);
当您动态发现方法时,这可能很有用 and/or 正在寻找过程中的其他工件,如注释。否则,只需通过 findVirtual
获取 MethodHandle
就足够了。
那么,你必须了解三种不同的函数类型:
- 目标方法句柄具有特定类型,在将方法句柄传递给工厂时隐式给出该类型。在你的情况下,它是
(MyClass,boolean) → void
- 与预期结果类型关联的通用函数类型
BiConsumer<MyClass, Boolean>
,即(MyClass,Boolean) → void
BiConsumer
接口的擦除类型,即(Object,Object) → void
只有正确指定所有三种类型才能告诉工厂它必须实现该方法
void accept(Object,Object)
的代码会将第一个参数转换为 MyClass
,将第二个参数转换为 Boolean
,然后将第二个参数解包为 boolean
,最终调用目标方法。
我们可以显式指定类型,但为了使代码尽可能可重用,我们可以在目标上调用 type()
,然后使用适配器方法。
wrap()
会将所有基本类型转换为它们的包装类型。不幸的是,这也意味着将 return 类型转换为Void
,因此我们必须再次将其设置回void
。
这为我们提供了 instantiatedMethodType 参数。 (比较the documentation)erase()
会将所有引用类型转换为Object
但保留所有原始类型。所以将它应用于 instantiatedMethodType 给我们擦除的类型。
这种简单的转换是否足够取决于特定的目标接口。对于java.util.function
中的接口,它是。
提高可重用性的另一点是为方法接收器使用实际类型参数 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();
}