[英]Java Reflection Casting Method ReturnType
為了提供一些背景知識,我正在創建一個小型依賴注入器並遇到將方法調用轉換回其返回類型的問題。 一個最小的例子是:
public class MinimalExample {
public static <T> void invokeMethod(Class<T> aClass) throws ReflectiveOperationException {
Optional<Method> myOptMethod = resolveMethod(aClass);
if (myOptMethod.isPresent()) {
Method myMethod = myOptMethod.get();
Object myInstance = myMethod.invoke(myMethod);
doSomething(myMethod.getReturnType(), myMethod.getReturnType().cast(myInstance));
}
}
private static <T> Optional<Method> resolveMethod(Class<T> aClass) {
return Stream.of(aClass.getMethods())
.filter(aMethod -> Modifier.isStatic(aMethod.getModifiers()))
.filter(aMethod -> aMethod.getParameterCount() == 0)
.findAny();
}
private static <U> void doSomething(Class<U> aClass, U anInstance) {
// E.g. Map aClass to anInstance.
}
}
這里的問題是doSomething
需要用Class<U>, U
調用,但它目前是用Class<capture of?>, capture of?
調用的。 由於invoke
方法的通配符返回類型。
我可以將doSomething
更改為doSomething(Class<?> aClass, Object anInstance)
但是我失去了類型安全性,這不一定是調用該方法的唯一地方。
我的問題是:考慮到顯式轉換,為什么編譯器不能推斷出它們具有相同的底層類型U
?
編輯(2021 年 3 月 9 日):
我冒昧地對字節碼進行了反編譯,以了解為什么rzwitserloot 的輔助方法確實解決了類型問題。 由於類型擦除,它們似乎是相同的調用。 我猜編譯器不夠聰明,無法在強制轉換后推斷它們是相同的捕獲類型,需要類型綁定來提供幫助。
我添加了以下功能
private static <U> void doSomethingWithTypeBinding(Class<U> aClass, Object anObject) {
doSomething(aClass, aClass.cast(anObject));
}
private static void doSomethingUnsafe(Class<?> aClass, Object anInstance) {}
我現在分別從第 15 行和第 16 行調用
doSomethingWithTypeBinding(myMethod.getReturnType(), myInstance);
doSomethingUnsafe(myMethod.getReturnType(), myMethod.getReturnType().cast(myInstance));
產生以下字節碼:
L5
LINENUMBER 15 L5
ALOAD 2
INVOKEVIRTUAL java/lang/reflect/Method.getReturnType ()Ljava/lang/Class;
ALOAD 3
INVOKESTATIC depinjection/handspun/services/MinimalExample.doSomethingWithTypeBinding (Ljava/lang/Class;Ljava/lang/Object;)V
L6
LINENUMBER 16 L6
ALOAD 2
INVOKEVIRTUAL java/lang/reflect/Method.getReturnType ()Ljava/lang/Class;
ALOAD 2
INVOKEVIRTUAL java/lang/reflect/Method.getReturnType ()Ljava/lang/Class;
ALOAD 3
INVOKEVIRTUAL java/lang/Class.cast (Ljava/lang/Object;)Ljava/lang/Object;
INVOKESTATIC depinjection/handspun/services/MinimalExample.doSomethingUnsafe (Ljava/lang/Class;Ljava/lang/Object;)V
// access flags 0xA
// signature <U:Ljava/lang/Object;>(Ljava/lang/Class<TU;>;TU;)V
// declaration: void doSomething<U>(java.lang.Class<U>, U)
private static doSomething(Ljava/lang/Class;Ljava/lang/Object;)V
L0
LINENUMBER 30 L0
RETURN
L1
LOCALVARIABLE aClass Ljava/lang/Class; L0 L1 0
// signature Ljava/lang/Class<TU;>;
// declaration: aClass extends java.lang.Class<U>
LOCALVARIABLE anInstance Ljava/lang/Object; L0 L1 1
// signature TU;
// declaration: anInstance extends U
MAXSTACK = 0
MAXLOCALS = 2
// access flags 0xA
// signature <U:Ljava/lang/Object;>(Ljava/lang/Class<TU;>;Ljava/lang/Object;)V
// declaration: void doSomethingWithTypeBinding<U>(java.lang.Class<U>, java.lang.Object)
private static doSomethingWithTypeBinding(Ljava/lang/Class;Ljava/lang/Object;)V
L0
LINENUMBER 33 L0
ALOAD 0
ALOAD 0
ALOAD 1
INVOKEVIRTUAL java/lang/Class.cast (Ljava/lang/Object;)Ljava/lang/Object;
INVOKESTATIC depinjection/handspun/services/MinimalExample.doSomething (Ljava/lang/Class;Ljava/lang/Object;)V
L1
LINENUMBER 34 L1
RETURN
L2
LOCALVARIABLE aClass Ljava/lang/Class; L0 L2 0
// signature Ljava/lang/Class<TU;>;
// declaration: aClass extends java.lang.Class<U>
LOCALVARIABLE anObject Ljava/lang/Object; L0 L2 1
MAXSTACK = 3
MAXLOCALS = 2
// access flags 0xA
// signature (Ljava/lang/Class<*>;Ljava/lang/Object;)V
// declaration: void doSomethingUnsafe(java.lang.Class<?>, java.lang.Object)
private static doSomethingUnsafe(Ljava/lang/Class;Ljava/lang/Object;)V
L0
LINENUMBER 37 L0
RETURN
L1
LOCALVARIABLE aClass Ljava/lang/Class; L0 L1 0
// signature Ljava/lang/Class<*>;
// declaration: aClass extends java.lang.Class<?>
LOCALVARIABLE anInstance Ljava/lang/Object; L0 L1 1
MAXSTACK = 0
MAXLOCALS = 2
由於運行時類型擦除,我們可以看到INVOKEVIRTUAL
直接轉換為INVOKESTATIC
看起來相同。
編輯(2021 年 3 月 12 日):
@Holger 在評論中指出, Method#getReturnType
返回一個Class<?>
。 因為它是通配符,所以從編譯器的角度來看,該方法不能保證后續方法調用返回具有相同捕獲類型的 Class。
類型變量是編譯器想象的虛構:它們無法在編譯后存活(擦除*)。 最好將它們視為連接事物。 永遠只在一個地方使用的類型變量是完全沒用的; 一旦它們出現在兩個地方,現在這很有用:它可以讓您將類型的多種用法聯系在一起,表示出現是相同的。 例如,對於 java.util.List ,您可以將.add(Obj thingToAdd)
的參數類型和.get(int idx)
的返回類型java.util.List
在一起。
在這里,您希望將myMethod.getReturnType
的Class<X>
與myInstance
變量鏈接在一起。 正如您所意識到的,這是不可能的,因為編譯器不知道它們最終會成為同一類型。 但是,通過調用Class<X>
的cast()
方法,我們可以解決這部分問題。
但是您仍然需要一些類型變量作為將事物聯系在一起的工具,而您沒有。 ?
類似於一次性使用型變量; Class<?> cls
和 myMethod.getReturnType().cast(myInstance)` 是“不同的”?s:是的,你的眼球可以看出它將是相同的類型,但 java 不能。 你需要一個類型變量。 你當然可以介紹一個:
private static <X> helper(Class<X> x, Object myInstance) {
doSomething(x, x.cast(myInstance));
}
將此方法添加到您的代碼中並調用此方法,而不是調用doSomething
。 我們在此處創建的<X>
用於將結果聯系在一起。
*) 當然,它們保留在公共簽名中,但在其他任何地方,在運行時 - 它們都會被刪除。
您可以在此處使用另一種選擇: doSomething
方法是private ,因此您可以完全控制它。 因此,您可以只將演員表移動到其中,這可以解決所有問題,或者您可以這樣編寫:
/** precondition: o must be an instance of c */
private static void doSomething(Class<?> c, Object o) {
}
由於它是一種私有方法,因此可以引入先決條件。 您可以完全控制調用此方法的所有代碼。 如果你真的想要,你可以添加一個運行時檢查( if (.c;isInstanceof(o)) throw new IllegalArgumentException("o not instance of c");
在頂部),但是否值得做的是一個 open在 java 生態系統中討論私有方法。 通常的判斷是不這樣做,或者使用assert
關鍵字。
注意:這有一些殘酷的空/可選處理。 萬一沒有找到要解決的方法..只是默默地什么都不做? 這就是 NPE 更好的原因:至少粗心的編碼會導致異常,而不是徒勞無功。
首先調用是:
Object myInstance = myMethod.invoke(null);
myMethod
是static
(正如您已經在resolveMethod
中找到的那樣),因此您需要傳遞一個null
,否則您將需要一個實例,而您沒有該實例。
然后修復你的例子,是相當微不足道的:
Method myMethod = myOptMethod.get();
Object myInstance = myMethod.invoke(null);
Class<?> cls = myMethod.getReturnType();
Object obj = myMethod.getReturnType().cast(myInstance);
doSomething(cls, obj);
該方法將定義更改為:
private static <U> void doSomething(Class<? extends U> aClass, U anInstance) {....}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.