从 LambdaMetafactory 创建 BiConsumer [英] Create BiConsumer from LambdaMetafactory

查看:96
本文介绍了从 LambdaMetafactory 创建 BiConsumer的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试通过 LambdaMetafactory 动态创建 BiConsumer 类型的方法引用.我试图应用在 https://上找到的两种方法www.cuba-platform.com/blog/think-twice-before-using-reflection/ - createVoidHandlerLambda 和这里 创建 BiConsumer 作为没有反射的 Field setter 霍尔格的答案.

I'm trying to dynamically create a method reference of type BiConsumer through LambdaMetafactory. 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.

但是在这两种情况下,我都遇到以下错误:

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)

我的代码是这样的:

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 如下所示:

Where MyClass looks like this:

public class MyClass {

    public boolean valid;

    public void setValid(boolean valid) {
        this.valid = valid;
        System.out.println("Called setValid");
    }
}

我将感谢您的帮助.

编辑#1.在咨询@Holger 后,我将 createSetter 方法修改为:

EDIT #1. After consulting @Holger I've modified createSetter method to:

@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;

Now this method does not throw the initial Exception althoug it seems that calling accept on this method reference has no effect. I do not see "Called setValid" in logs for this call. Only for MyClass::setValid;

推荐答案

请注意,您将 getMethodcaller.findVirtual(...) 用于同一方法是多余的.如果你的起点是一个Method,你可以使用unreflect,例如

Note that your use of getMethod and caller.findVirtual(…) for the same method is redundant. If your starting point is a Method, you may use unreflect, e.g.

Method method = classType.getMethod("setValid", boolean.class);
MethodHandles.Lookup caller = MethodHandles.lookup();
MethodHandle target = caller.unreflect(method);

当您动态发现方法和/或在过程中寻找其他工件(如注释)时,这可能很有用.否则,只需通过 findVirtual 获取 MethodHandle 就足够了.

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.

然后,您必须了解三种不同的函数类型:

Then, you have to understand the three different function types:

  • 目标方法句柄具有特定类型,该类型在将方法句柄传递给工厂时隐式给出.在您的情况下,它是 (MyClass,boolean) → void
  • 与预期结果类型相关联的通用函数类型
    BiConsumer,即 (MyClass,Boolean) → void
  • BiConsumer 接口的擦除类型,即 (Object,Object) → void
  • 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
  • The generic function type associated with the intended result type
    BiConsumer<MyClass, Boolean>, which is (MyClass,Boolean) → void
  • The erased type of the BiConsumer interface, which is (Object,Object) → void

只有正确指定了所有三种类型才告诉工厂它必须实现该方法
void accept(Object,Object) 带有将第一个参数转换为 MyClass 并将第二个参数转换为 Boolean 的代码,然后解开第二个参数boolean,最终调用目标方法.

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.

我们可以显式指定类型,但为了使代码尽可能可重用,我们可以在目标上调用 type(),然后使用适配器方法.

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.

  • wrap() 将所有原始类型转换为其包装类型.不幸的是,这也意味着将返回类型转换为 Void,因此我们必须再次将其设置回 void.
    这为我们提供了 instantiatedMethodType 参数.(与 文档)
  • erase() 会将所有引用类型转换为 Object,但保留所有原始类型.因此,将其应用于 instantiatedMethodType 为我们提供了擦除类型.
    这种简单的转换是否足够取决于特定的目标接口.对于 java.util.function 中的接口,它是.
  • wrap() will convert all primitive types to their wrapper type. Unfortunately, this also implies converting the return type to Void, so we have to set it back to void again.
    This gives us the instantiatedMethodType parameter. (Compare with the documentation)
  • erase() will convert all reference types to Object but leave all primitive types as-is. So applying it to the instantiatedMethodType gives us the erased type.
    It depends on the particular target interface whether this simple transformation is sufficient. For the interfaces in java.util.function, it is.

提高可重用性的另一点是为方法接收器类使用实际类型参数,因为我们无论如何都将类作为参数:

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:

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的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆