从LambdaMetafactory创建BiConsumer。

问题描述 投票:0回答:1

我试图通过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的调用。

java reflection method-reference
1个回答
4
投票

请注意,您使用的 getMethodcaller.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();
}
© www.soinside.com 2019 - 2024. All rights reserved.