2017-05-23 99 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變體一樣,它也有許多其他的東西,如果沒有編譯器告訴你,它可能會做錯的事情,它實際上並不重要。