ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

java – 使用MethodHandleProxies的正确方法

2019-06-11 08:48:05  阅读:294  来源: 互联网

标签:java reflection java-8 functional-interface


在我正在研究的Java项目中,我正在动态加载类,然后使用反射API来查找和执行具有某些注释的类的方法.

执行实际执行的代码仅在Java-8功能接口方面工作(出于兼容性原因),因此我需要一个中间阶段,使用反射发现的Method实例转换为适当的功能接口.我使用MethodHandleProxies课程实现了这一目标.

出于兼容性原因,所讨论的功能接口是通用接口.当使用MethodHandleProxies.asInterfaceInstance方法时,这会导致“未经检查的转换”警告,因为该方法返回“裸”接口.

以下是重现所涉及的主要步骤的简短示例:

import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandleProxies;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
import java.util.Arrays;

public class TestClass {
    private String prefix;

    public static void main(String[] args) throws IllegalAccessException, NoSuchMethodException, SecurityException {
        // Use reflection to find method.
        Method method = Arrays.stream(TestClass.class.getDeclaredMethods()) // Stream over methods of ConsumerClass
                .filter(m -> m.isAnnotationPresent(Marker.class)) // Retain only methods with @Marker annotation
                .findFirst().get(); // Get first such method (there is only one in this case)

        // Convert method to "MethodInterface" functional interface.
        MethodHandle handle = MethodHandles.lookup().unreflect(method);
        MethodInterface<TestClass, String> iface = MethodHandleProxies.asInterfaceInstance(MethodInterface.class, handle);

        // Call "testMethod" via functional interface.
        iface.call(new TestClass("A"), "B");
    }

    public TestClass(String prefix) {
        this.prefix = prefix;
    }

    @Marker
    public void testMethod(String arg) {
        System.out.println(prefix + " " + arg);
    }

    @Retention(RUNTIME)
    public @interface Marker { }

    @FunctionalInterface
    public interface MethodInterface<I,V> {
        void call(I instance, V value);
    }
}

此代码编译并运行,但在分配给iface时有未经检查的转换警告.

使MethodInterface非泛型可以解决这个特殊问题,但这意味着它将不再适用于任意类型的方法引用(这对于代码的其他部分是可取的).

例如,使用TestClass和MethodInterface的上述定义,以下行编译:

MethodInterface<TestClass,String> iface = TestClass::testMethod;

但是,更改为MethodInterface的以下定义会打破这个:

@FunctionalInterface
public interface MethodInterface {
    void call(Object inst, Object value);
}

将TestClass :: testMethod分配给此接口的实例不会编译,因为参数的类型错误.

在我看来,我有三个选择:

>简单地接受警告.
>在作业中添加@SuppressWarnings注释.
>提出另一种类型安全的方法.

我尝试确保我的代码没有产生任何警告(以最大限度地减少错误的机会),所以我不喜欢选项1.选项2感觉它只是“打破裂缝”,但如果绝对必要的话可以接受.所以我的首选方案是提出一种不同的方法.

是否存在本质上类型安全的不同方法?

解决方法:

将反射生成的实例分配给参数化通用接口是未经检查的操作,因为无法确保生成的类满足该参数化接口.实际上,MethodHandleProxies背后的实现根本不关心这个签名.所以有一个警告是正确的,当你确信你做的一切正确时,抑制它,将抑制限制在可能的最小范围内是最好的(或不可避免的)解决方案.

您可以创建一个可重新生成的子接口,例如interface Specific特定扩展MethodInterface< TestClass,String> {},用于代码生成,从编译器的角度来看没有未经检查的操作,但它不会改变事实,即代理根本不关心正确性.

顺便说一句,如果您的目标接口是功能接口,则可以使用LambdaMetafactory而不是MethodHandleProxies.代码生成稍微复杂一些,但是结果类可能比更通用的代理更有效(甚至实际上在今天的JRE中).

// Use reflection to find method.
Method method = Arrays.stream(TestClass.class.getDeclaredMethods())
        .filter(m -> m.isAnnotationPresent(Marker.class))
        .findFirst().get();

// Convert method to "MethodInterface" functional interface.
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle handle = lookup.unreflect(method);
MethodInterface<TestClass, String> iface;
try {
    iface = (MethodInterface<TestClass, String>)LambdaMetafactory.metafactory(lookup,
            "call", MethodType.methodType(MethodInterface.class),
            MethodType.methodType(void.class, Object.class, Object.class),
            handle, handle.type())
          .getTarget().invoke();
} catch(RuntimeException|Error|ReflectiveOperationException|LambdaConversionException ex) {
    throw ex;
}
catch (Throwable ex) {
    throw new AssertionError(ex);
}
// Call "testMethod" via functional interface.
iface.call(new TestClass("A"), "B");

这个代码不会生成未经检查的警告,这只是巧合.它实际上有一个未经检查的操作,但是像MethodHandleProxies变体一样,它还有很多其他的东西,你可以做错而不需要编译器告诉你,它实际上并不重要.

标签:java,reflection,java-8,functional-interface
来源: https://codeday.me/bug/20190611/1217457.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有