Java 運行時如何獲取泛型參數的類型
在 Java 中對于下面最簡單的泛型類
class A<T> {
public void foo() {
//如何在此處獲得運行時 T 的具體類型呢?
}
}
設想我們使用時
new A<String>().foo();
是否能在 foo() 方法中獲得當前的類型是 String 呢?答案是否定的,不能。在 foo() 方法中 this 引用給不出類型信息, this.getClass() 就更不可能了,因為 Java 的泛型不等同于 C++ 的模板類, this.getClass() 實例例是被所有的不同具體類型的 A 實例(new A<String>(), new A<Integer>() 等) 共享的,所以在字節碼中類型會被擦除到上限。
我們可以在 IDE 的調試時看到這個泛型類的簽名
或者用 javap -v cc.unmi.A 可以查看到類 A 的泛型簽名
Signature: #17 // <T:Ljava/lang/Object;>Ljava/lang/Object;
為什么說是擦除到上限呢?并不是泛型在字節碼中都表示為 Object , 看下面的例子,假如 A 聲明如下
class A<T extends Number> {
}
再用 javap -v cc.unmi.A 來看泛型簽名
Signature: #18 // <T:Ljava/lang/Number;>Ljava/lang/Object;
也就是說在上面的 foo() 方法中無法獲得當前的類型,我們必須給它加個參數 T
public void foo(T t) {
t.getClass();
}
了解了 Java 泛型機制是如何擦除類型的,我們接下來的問題就是如何通過反射獲得泛型簽名中的類型,一般會在繼承或實現泛型接口時會用到它。
繼承一個泛型基類
package cc.unmi;
class A<T, ID> {
}
class B extends A<String, Integer> {
}
public class Generic {
public static void main(String[] args) {
System.out.println(B.class.getGenericSuperclass());
}
}
上面的代碼輸出是
cc.unmi.A<java.lang.String, java.lang.Integer>
所以要獲得這兩個類型是可行的,設置了斷點
這張圖可以看到 B.class.getGenericSuperclass() 得到的實際類型是 ParameterizedTypeImpl 通過它就可以獲得 actualTypeArguments 了。代碼就是
ParameterizedType parameterizedType = (ParameterizedType) B.class.getGenericSuperclass();
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
for(Type actualTypeArgument: actualTypeArguments) {
System.out.println(actualTypeArgument);
}
上面的代碼輸出
class java.lang.String
class java.lang.Integer
我們不妨用 javap -v cc.unmi.B 的泛型簽名
Signature: #12 // Lcc/unmi/A<Ljava/lang/String;Ljava/lang/Integer;>;
實現一個泛型接口
這時與繼承一個泛型基類的情況略有不同,如下關系,A 是一個泛型接口
interface A<T, ID> {
}
class B implements A<String, Integer> {
}
該如何反射獲得 B 的參數類型呢,用上面的方法已不可行, B.class.getGenericSuperclass() 已不是一個 ParameterizedTypeImpl 而是一個 Object 類型。現在需要另一個方法 getGenericInterfaces(): Type[] 它得到一個 Type 數組,代表類實現的多個接口,因為我們這兒只實現了一個接口,所以取第一個元素,它的類型是我們已見過的 ParameterizedTypeImpl ,
因此我們用來獲得實現接口而來的泛型參數的代碼就是
ParameterizedType parameterizedType = (ParameterizedType) B.class.getGenericInterfaces()[0];
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);
}
同樣能得到上面的一樣的結果。
總結一下
- 如果是繼承基類而來的泛型,就用 getGenericSuperclass() , 轉型為 ParameterizedType 來獲得實際類型
- 如果是實現接口而來的泛型,就用 getGenericInterfaces() , 針對其中的元素轉型為 ParameterizedType 來獲得實際類型
- 我們所說的 Java 泛型在字節碼中會被擦除,并不總是擦除為 Object 類型,而是擦除到上限類型
- 能否獲得想要的類型可以在 IDE 中,或用 javap -v <your_class> 來查看泛型簽名來找到線索
來自:http://unmi.cc/java-how-to-get-generic-type/