当前位置: 首页 > 知识库问答 >
问题:

从LambdaMetafactory创建BiConsumer

施刚毅
2023-03-14

我正在尝试通过LambdaMetafactory动态创建BiConsumer类型的方法引用。我试图应用 https://www.cuba-platform.com/blog/think-twice-before-using-reflection/ 上的两种方法 - createVoidHandlerLambda和这里的Create BiConsumer作为字段设置器,而不反映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);
    }

}

我的类是这样的:

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);
    }

现在此方法不会抛出初始异常 althoug,似乎对此方法引用调用 accept 不起作用。我在此调用的日志中看不到“调用的 setValid”。仅适用于 MyClass::setValid;

共有1个答案

倪阳飇
2023-03-14

请注意,您使用getMethod调用方。相同方法的findVirtual(…)是多余的。如果您的起点是方法,您可以使用unreflect,例如。

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

当您动态发现方法和/或在流程中查找其他工件(如注释)时,这可能很有用。否则,只需通过find虚拟获取方法处理就足够了。

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

    目标方法句柄
  • 具有特定类型,在将方法句柄传递给工厂时隐式提供该类型。在您的情况下,它是(MyClass,布尔值)

只有正确指定所有三种类型才能告诉工厂,它必须使用代码实现方法
空接受(Object, Object),该代码将第一个参数转换为Myclass,第二个参数转换为布尔,然后将第二个参数解包为布尔,以最终调用目标方法。

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

  • 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();
}

 类似资料:
  • 问题内容: 我试图显式地使用LambdaMetafactory.metafactory,我不明白为什么它只能与Runnable功能接口一起使用。例如,以下代码完成了预期的工作(打印“ hello world”): 当我尝试使用其他功能接口(例如供应商)时,就会出现问题。以下代码不起作用: 这两个代码段不应该以相似的方式工作吗,这是第二个代码段中的问题? 此外,以下应等效的代码也可以正常工作: 编辑

  • 问题内容: 我创建了一个简单的POJO: 经过一番搜索,我发现了这一点: 但是有了这个我得到了错误: 我找到了另一个解决方案: 但是这种方法不存在。 那么如何将POJO转换为JSON? 问题答案: 只需使用java Gson API : 然后您可以从json 创建一个,如下所示: 看看 GSON用户指南 ,这 SIMPLE GSON实例 的详细信息。

  • 大家好 错误:org.json.jsonException:不是原始数组:类org.json.jsonArray 我不明白为什么这是一个问题。我想从对象中获得JSONArray。

  • 我是一个xml和XSL的新手,使用遗留平台... 我正在寻找一个从XPath创建xml的解决方案。碰巧看到这篇文章如何从一组XPath表达式生成XML文件?帮了我大忙。 与“comments”一节中讨论的请求类似,我试图将整个XSLT作为字符串传递,并使用Saxon作为sting back接收结果。接收结果为字符串,没有问题。但是当将XSL作为字符串传递时,它会抱怨“document()”,它是

  • 问题内容: 我们正在从Oracle JDK / JRE切换到OpenJDK。现在,我只找到了JDK,但我也想从OpenJDK获得JRE。这是为了在客户端上安装我们的应用程序而无需完整的JDK。 有没有一种方法可以从适用于Windows X64的OpenJDK创建JRE包? 问题答案: 受到文章使用jlink为非模块化应用程序构建Java运行时的启发,我使用了以下命令: 获取所有可用的openjdk

  • 问题内容: 我正在尝试使用jQuery UI创建动态菜单。 我将从JSON文件中获取条目并创建菜单项。在大规模尝试之前,我决定做一个小演示。这是我的小提琴,它按照我想要的方式工作。现在,我无法将其与JSON文件一起使用。 工作场所 这是JSON 我如何使用JSON中的值设计我的整个菜单,其中Li类似于以下内容。 编辑: 这个问题听起来好像我还没有尝试过,但是我已经尝试过。它只是我无法理解的JSON