java – 使用MethodHandleProxies的正确方法
作者:互联网
在我正在研究的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