正确使用MethodHandleProxies的方法

时间:2017-05-23 04:34:46

标签: 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非泛型可以解决这个特殊问题,但这意味着它将不再适用于任意类型的方法引用(这对于代码的其他部分是可取的)。

例如,对于TestClassMethodInterface的上述定义,以下行编译:

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

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

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

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

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

  1. 简单地接受警告。
  2. 在作业中添加@SuppressWarnings注释。
  3. 提出另一种类型安全的方法。
  4. 我尽量确保我的代码没有产生任何警告(以最大限度地减少错误的机会),所以我不喜欢选项1.选项2感觉它只是“打破裂缝”,但是可以接受如果绝对必要。所以我的首选方案是采用不同的方法。

    是否有一种本质上属于类型安全的不同方法?

3 个答案:

答案 0 :(得分:3)

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

你可以创建一个可再生的子界面,例如interface Specific extends 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变体一样,它还有很多其他的东西你可以做错,而编译器没有告诉你,它实际上并不重要。

答案 1 :(得分:2)

我发现有趣的是你可以让一个函数将一个对象转换为特殊类型以避免未经检查的警告,例如:

object_id

答案 2 :(得分:2)

你遇到的问题是:

  MethodInterface.class

返回Class<MethodInterface>。如果允许这样的语法会很酷:

Class<MethodInterface<<TestClass, String>>> clazz =  
             MethodInterface<TestClass, String>.class;

但是由于类型擦除它不会有很多意义。

另一方面,这将在没有警告的情况下编译(不一定有效):

 String result = MethodHandleProxies.asInterfaceInstance(String.class, handle);

据我所知,你仍然坚持这个警告。