Java 泛型擦除的理解及如何獲取泛型的實際類型

泛型

泛型是JDK1.5之后的一項新增特性, 它的本質是參數化類型(Parametersized Type)的應用,
即所操作的數據類型被指定為一個參數,這種參數類型可以用在類、接口和方法的創(chuàng)建中,分別為泛型類、泛型接口和泛型方法。
Java中的泛型只在程序源代碼中存在, 在編譯后的字節(jié)碼文件中就已經替換為原來的原生類型(Raw Type), 并在相應的地方插入強制類型裝換代碼。
對于運行期的Java語言來說,ArrayList<int> 與 ArrayList<String> 就是同一個類,泛型技術實際上就是Java語言的一顆語法糖,Java語言中的泛型實現方法稱為類型擦除,基于這種方法的實現的泛型稱為偽泛型。

java 字節(jié)碼層面

源代碼

public class MyListTest {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("test");
        String list1 = list.get(0);
        System.out.println(list1);
        System.out.println(list);
    }
}

編譯命令

javac -encoding UTF-8 MyListTest.java

編譯為class文件

public class MyListTest {
    public MyListTest() {
    }

    public static void main(String[] var0) {
        ArrayList var1 = new ArrayList();
        var1.add("test");
        String var2 = (String)var1.get(0); // 這里插入強制類型轉換
        System.out.println(var2);
        System.out.println(var1);
    }
}

javap查看字節(jié)碼

javap -c MyListTest
警告: 二進制文件MyListTest包含com.james.example.MyListTest
Compiled from "MyListTest.java"
public class com.james.example.MyListTest {
  public com.james.example.MyListTest();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class java/util/ArrayList
       3: dup
       4: invokespecial #3                  // Method java/util/ArrayList."<init>":()V
       7: astore_1
       8: aload_1
       9: ldc           #4                  // String test
      11: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
      16: pop
      17: aload_1
      18: iconst_0
      19: invokeinterface #6,  2            // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;  這里獲取的是Object類型
      24: checkcast     #7                  // class java/lang/String 類型轉換指令
      27: astore_2
      28: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
      31: aload_2
      32: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      35: getstatic     #8                  /

Java中獲取泛型的類型信息

Java泛型的引入,各種場景(虛擬機解析, 反射等)下的方法調用都可能對原有的基礎產生影響和新需求, 如在泛型類中如何獲取傳入的參數化類型等?;诖耍琂CP組織對虛擬機規(guī)范做出了相應的修改,引入了諸如Signature, LocalVariableType Table 等新屬性解決伴隨泛型而來的參數化類型的識別問題。
Signature 是其中最重要的一項屬性,作用是存儲一個方法在字節(jié)碼層面的特征簽名,這個屬性中保存的參數類型不是原生類型,而是包括了參數化類型的信息。

<>: 念做typeof
List<E>: E稱為類型參數變量
List<Integer>: Integer稱為實際類型參數
List<Integer>: 整個List<Integer>稱為參數化類型(對應著java.lang.reflect.ParameterizedType接口)

實例化子類中獲取泛型

定義一個MyType

public class MyType<T> {
    public Class<T> getTClass() {
        return (Class<T>)((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0];
    }
}

測試

        MyType<String> myType = new MyType<String>(){}; //注意這里是實例化的
        // 在類的外部獲取
        Type actualTypeArguments = ((ParameterizedType) myType.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
        Type rawType = ((ParameterizedType) myType.getClass().getGenericSuperclass()).getRawType();
        System.out.println(actualTypeArguments);
        System.out.println(rawType);
        System.out.println(myType.getTClass());

測試結果

class java.lang.String
class com.james.example.type.MyType
class java.lang.String

未實例化子類無法獲取泛型

測試

MyType<String> myType = new MyType<>(); //注意這里是未實例化的
Type actualTypeArguments = ((ParameterizedType) myType.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
Type rawType = ((ParameterizedType) myType.getClass().getGenericSuperclass()).getRawType();
System.out.println(actualTypeArguments);
System.out.println(rawType);
System.out.println(myType.getTClass());

結果

Exception in thread "main" java.lang.ClassCastException: class java.lang.Class cannot be cast to class java.lang.reflect.ParameterizedType (java.lang.Class and java.lang.reflect.ParameterizedType are in module java.base of loader 'bootstrap')
    at com.james.example.type.MyListTest.main(MyListTest.java:23)

List測試

ArrayList<String> list3 = new ArrayList<String>(){}; 
System.out.println(((ParameterizedType)list3.getClass().getGenericSuperclass()).getRawType());
System.out.println(((ParameterizedType)list3.getClass().getGenericSuperclass()).getActualTypeArguments()[0]);
        
ArrayList<String> list6 = new ArrayList<>();// 這里new ArrayList<String>() 結果也是同new ArrayList<>()
System.out.println(((ParameterizedType)list6.getClass().getGenericSuperclass()).getRawType());
System.out.println(((ParameterizedType)list6.getClass().getGenericSuperclass()).getActualTypeArguments()[0]);
class java.util.ArrayList
class java.lang.String
class java.util.AbstractList
E

總結

getGenericInterfaces()
getGenericSuperclass()
import java.lang.reflect.ParameterizedType; getActualTypeArguments()
import java.lang.reflect.Type;

Signature屬性的出現,Java泛型擦除法所謂的擦除,只是對方法的Code屬性中的字節(jié)碼進行擦除,實際上元數據中還是保留了泛型信息,這也是我們能通過反射手段獲取參數化類型的根本依據。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容