简体   繁体   English

从 LambdaMetafactory 创建 BiConsumer

[英]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.请注意,您对同一方法使用getMethodcaller.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 类型:

  • The target method handle has a specific type which is given implicitly when passing the method handle to the factory.目标方法句柄具有特定类型,在将方法句柄传递给工厂时隐式给出。 In your case, it is (MyClass,boolean) → void在您的情况下,它是(MyClass,boolean) → void
  • The generic function type associated with the intended result type与预期结果类型关联的通用 function 类型
    BiConsumer<MyClass, Boolean> , which is (MyClass,Boolean) → void BiConsumer<MyClass, Boolean> ,即(MyClass,Boolean) → void
  • The erased type of the 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
    This gives us the instantiatedMethodType parameter.这为我们提供了instantiatedMethodType参数。 (Compare with the documentation ) (与文档比较)
  • 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会给我们擦除类型。
    It depends on the particular target interface whether this simple transformation is sufficient.这种简单的转换是否足够取决于特定的目标接口。 For the interfaces in 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM