2017-05-23 94 views
4

在我目前正在处理的Java项目中,我动态加载类,然后使用反射API来查找并执行具有某些注释的类的方法。使用MethodHandleProxies的正确方法

执行实际执行的代码专门用于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. 想出另一种类型安全的方法。

我尽力确保没有通过我的代码生成的警告(以尽量减少错误机会),所以我并不热衷于选项1选项2个感觉它简直是“掩盖了裂缝”,但如果绝对必要的话可以接受。所以我最喜欢的选择是想出一个不同的方法。

是否有不同的方法是天生类型安全的?

+0

显然'asInterfaceInstance'不支持泛型。你能解释为什么你需要一个通用的'MethodInterface'?在这种情况下,非泛型将会做到,并且很难想象为什么以及如何在代码的其余部分中使用通用泛型。 – CoronA

+0

@CoronA:需要一个通用的'MethodInterface'来支持方法引用(在代码中的其他地方使用) - 请参阅更新后的问题。 – Mac

回答

2

我已经找到了有趣的事情是,你可以让一个函数对象转换的特殊类型,以避免未经检查的警告,例如:

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

MethodInterface<TestClass, String> iface = casting.apply(
    MethodHandleProxies.asInterfaceInstance(targetType, handle) 
); 
+2

这很有趣,它甚至不会为jdk-8编译。 – Eugene

+0

@Eugene对不起,我有一个错别字。这个怎么样?我刚刚发现它可以避免未经检查的警告。 –

+2

那么,因为这*是一个未经检查的操作,所以没有警告可以被认为是编译器错误,而不是真正的解决方案。 – Holger

2

您所遇到的问题是这样的:

MethodInterface.class 

返回Class<MethodInterface>。如果这样的语法将允许这将是冷静:

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

,但那就不是赚了很多,因为类型擦除的感觉。

在另一方面,这将编译没有警告(不一定工作):

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

至于我可以告诉你被卡住的警告。

+3

'String result = MethodHandleProxies.asInterfaceInstance(String.class,handle);'不会产生编译器警告,但没有警告并不足以说明“这会起作用”。 – Holger

3

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

您可以创建一个可调整的子界面,例如, interface Specific extends MethodInterface<TestClass,String> {},将其用于代码生成,从编译器的角度来看没有未经检查的操作,但它不会改变事实,即代理根本不关心正确性。顺便说一句,如果你的目标界面是一个功能界面,你可以使用LambdaMetafactory而不是MethodHandleProxies来代替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变体一样,它也有许多其他的东西,如果没有编译器告诉你,它可能会做错的事情,它实际上并不重要。