Java 運行時如何獲取泛型參數的類型

LuaPogue 7年前發布 | 15K 次閱讀 泛型 Java 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);
}

同樣能得到上面的一樣的結果。

總結一下

  1. 如果是繼承基類而來的泛型,就用 getGenericSuperclass() , 轉型為 ParameterizedType 來獲得實際類型
  2. 如果是實現接口而來的泛型,就用 getGenericInterfaces() , 針對其中的元素轉型為 ParameterizedType 來獲得實際類型
  3. 我們所說的 Java 泛型在字節碼中會被擦除,并不總是擦除為 Object 類型,而是擦除到上限類型
  4. 能否獲得想要的類型可以在 IDE 中,或用 javap -v <your_class>   來查看泛型簽名來找到線索

 

來自:http://unmi.cc/java-how-to-get-generic-type/

 

 本文由用戶 LuaPogue 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!