使用 MethodHandleProxies 的正确方法

Mac*_*Mac 5 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

此代码可以编译并运行,但在分配给iface.

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

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

MethodInterface<TestClass,String> iface = TestClass::testMethod;
Run Code Online (Sandbox Code Playgroud)

然而,更改为以下定义会MethodInterface打破这一点:

@FunctionalInterface
public interface MethodInterface {
    void call(Object inst, Object value);
}
Run Code Online (Sandbox Code Playgroud)

分配TestClass::testMethod给该接口的实例无法编译,因为参数的类型错误。

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

  1. 只需接受警告即可。
  2. @SuppressWarnings向作业添加注释。
  3. 提出一种替代的类型安全方法。

我尝试确保我的代码不会生成警告(以最大程度地减少出现错误的机会),因此我不热衷于选项 1。选项 2 感觉就像它只是“掩盖裂缝”,但如果绝对必要,也是可以接受的。所以我的首选选择是想出一种不同的方法。

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

hol*_*ava 2

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

Class<MethodInterface> targetType = MethodInterface.class;
Function<Object,MethodInterface<TestClass,String>> casting=targetType::cast;

MethodInterface<TestClass, String> iface = casting.apply(
     MethodHandleProxies.asInterfaceInstance(targetType, handle)
);
Run Code Online (Sandbox Code Playgroud)

  • 这很有趣,它甚至不能用 jdk-8 为我编译。 (2认同)
  • 好吧,由于这是一个未经检查的操作,因此没有警告可以被视为编译器错误,而不是真正的解决方案。 (2认同)