![](/img/trans.png)
[英]Java Reflection getGenericReturnType() error in Java 1.7
[英]Reflection - Method::getGenericReturnType no generic - visbility
我有一個奇怪的問題,即Method::getGenericReturnType()無法檢索泛型類型信息。
這是最小化版本:
public class Test {
public static void main(String[] args) {
Method method = B.class.getMethods()[0]; // foo() method inherited from A
System.out.println(method.getGenericReturnType());
}
static class A {
public List<String> foo() { return null; }
}
public static class B extends A {}
}
Output 是
java.util.List
沒有任何通用類型信息。 這對我來說似乎很奇怪。
但是,將A
的可見性更改為public
並且它正確地給出了
java.util.List<java.lang.String>
我不知道這是一個錯誤還是實際預期的行為。 如果是預期的,其背后的原因是什么?
我正在使用來自 AdoptOpenJDK 的 OpenJDK 15:
// javac
javac 15.0.1
// java
openjdk version "15.0.1" 2020-10-20
OpenJDK Runtime Environment AdoptOpenJDK (build 15.0.1+9)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 15.0.1+9, mixed mode, sharing)
朋友也可以復制它:
我做了很多實驗,發現A
和B
之間唯一觸發此問題的可見性組合是B
是public
而A
不是public
。 任何其他組合,它都會再次按預期工作。 所以只有
public
,A protected
public
,A package-visible
public
,A private
表現出奇怪的行為。
我嘗試在不同的文件中移動代碼,將其放入不同的包中,在這里和那里添加或刪除static
並且沒有任何改變。
我還檢查了該方法的源代碼,即
public Type getGenericReturnType() {
if (getGenericSignature() != null) {
return getGenericInfo().getReturnType();
} else { return getReturnType();}
}
其中getGenericSignature()
依賴於在構造Method
實例期間設置的String signature
。 在上述情況下,由於某種原因,它似乎是null
。
我剛剛檢查了類的字節碼,並在B.class
中找到了這個:
public Test$B();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method Test$A."<init>":()V
4: return
LineNumberTable:
line 16: 0
public java.util.List foo();
descriptor: ()Ljava/util/List;
flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #7 // Method Test$A.foo:()Ljava/util/List;
4: areturn
LineNumberTable:
line 16: 0
對我來說,這看起來像B
,出於某種原因,創建了另一個方法foo()
,它只是將方法調用轉發給A
s foo()
,因此沒有通用類型信息。
此外,當調用B.getDeclaredMethods()
它實際上返回一個方法,即
public java.util.List Test$B.foo()
即使此方法應該排除繼承的方法(來自文檔):
返回包含 Method 對象的數組,該對象反映 class 或由此 Class object 表示的接口的所有聲明方法,包括公共、受保護、默認(包)訪問和私有方法,但不包括繼承方法。
如果B
真的創建了一個包裝器方法,那么現在這將是有意義的。
但是,它為什么要創建這樣的方法? 是否有解釋此行為的 JLS 部分?
讓我們在這里慢慢來。 首先,這就是為什么要生成橋接方法的原因。 即使你放棄generics,仍然會有橋接方法。 也就是說,這段代碼:
static class A {
public String foo() { return null; }
}
public static class B extends A {}
仍將生成帶有ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
的foo
方法。 您可以閱讀錯誤描述並了解為什么需要這樣做。
另一方面,如果您將A
public
,則不會生成這樣的方法,原因應該很明顯,考慮到前面的錯誤解釋(我希望)。 所以想法是,如果你有一個非公共的class, javac
將為上面的場景生成一個橋接方法。
現在,如果您將 generics 添加到合成方法的組合中,事情就會開始顯現出來。 例如,你有這個:
interface WithGeneric<T> {
public WithGeneric<T> self(T s);
}
public class Impl implements WithGeneric<String> {
@Override
public WithGeneric<String> self(String s) {
return null;
}
}
在Impl.class
中也會生成一個橋接方法,但它的聲明將是接口的擦除。 換句話說,在Impl.class
中會有兩種方法:
public WithGeneric<String> self(String) {...}
public WithGeneric self(Object) {...}
如果你把這兩件事粘合起來:
在非公共類的情況下,將創建一個橋接方法(以便反射起作用)
在 generics 的情況下,將創建一個擦除的橋接方法(以便擦除的調用可以工作)
事情會(以某種方式)有意義。
當您聲明A
public 時, B.class.getMethods()[0]
沒有引用B
; 它引用A.foo()
,其中聲明了方法並獲得了類型,因為存在signature
。
聲明A
非公開強制B.class.getMethods()[0]
參考B.foo()
。
由於沒有聲明繼承方法,因此無法通過對getGenericReturnType
的調用獲取類型,因為類型擦除應用於 generics。
在編譯時:
List<String> foo()
變為List foo()
。
這就是B
可以提供給您的有關方法返回類型的所有信息,因為剛剛刪除了B.foo()
中的簽名。
A
和B
都為foo()
持有相同的返回類型: java.util.List
(無參數類型)
這是A.foo()
聲明的返回類型。 與B.foo()
相同:
不同之處在於A
具有有效的簽名,因此它將通過添加參數類型來完成對getGenericReturnType()
的調用的結果。
在B
的情況下,它只會顯示它所知道的:只是返回類型。
試圖解決能見度難題時,我感到非常頭疼。 有關為什么會發生這種情況的完整解釋,請查看 Eugene 的答案。 說真的,你可以從這種人身上學到很多東西。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.