Java泛型原理:擦除法

關於泛型是什麼以及怎麼使用本文不在贅述。在04年發佈的jdk5中,Java支持了泛型這個重要的特性。
Java裏的泛型實現方式是擦拭法(Type Erasure),所謂擦拭法是指:虛擬機對泛型其實一無所知,即JVM不認識T,所有的工作都是編譯器做的。
整個過程大概描述就是:Java代碼中編寫的泛型T,會被編譯器重寫爲Object存儲到字節碼中。在獲取T類型的數據時,又是從Object額外轉換類型爲T。
 
爲了驗證和理解上述過程,我們編寫一段演示代碼:
import java.util.ArrayList;

public class MyGenericList<T> {
    private ArrayList<T> _listContainer = new ArrayList<T>();

    public void add(T tt) {
        this._listContainer.add(tt);
    }

    public T Get(int positionIndex) {
        return _listContainer.get(positionIndex);
    }
}

 


public class Main {

    public static void main(String[] args) {
        var tt1 = new MyGenericList<String>();
        var tt2 = new MyGenericList<Integer>();

        tt1.add("hello1");
        tt2.add(0);

        System.out.println(tt1.Get(0));
        System.out.println(tt2.Get(0));
        System.out.println("tt1.getClass() == tt2.getClass() ="+(tt1.getClass() == tt2.getClass()));

        if(tt1 instanceof MyGenericList<String>){
            System.out.println("tt1 instanceof MyGenericList<String>  =true");
        }
    }
}

 

程序執行結果:

hello1
0
tt1.getClass() == tt2.getClass() =true
tt1 instanceof MyGenericList<String>  =true
 
爲了進一步驗證編譯器的結果,使用javacp反編譯字節碼得到內容如下:
Compiled from "MyGenericList.java"
public class MyGenericList<T> {
  public MyGenericList();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: new           #7                  // class java/util/ArrayList
       8: dup
       9: invokespecial #9                  // Method java/util/ArrayList."<init>":()V
      12: putfield      #10                 // Field _listContainer:Ljava/util/ArrayList;
      15: return

  public void add(T);
    Code:
       0: aload_0
       1: getfield      #10                 // Field _listContainer:Ljava/util/ArrayList;
       4: aload_1
       5: invokevirtual #16                 // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z
       8: pop
       9: return

  public T Get(int);
    Code:
       0: aload_0
       1: getfield      #10                 // Field _listContainer:Ljava/util/ArrayList;
       4: iload_1
       5: invokevirtual #20                 // Method java/util/ArrayList.get:(I)Ljava/lang/Object;
       8: areturn
}

  

Compiled from "Main.java"
public class Main {
  public Main();
    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           #7                  // class MyGenericList
       3: dup
       4: invokespecial #9                  // Method MyGenericList."<init>":()V
       7: astore_1
       8: new           #7                  // class MyGenericList
      11: dup
      12: invokespecial #9                  // Method MyGenericList."<init>":()V
      15: astore_2
      16: aload_1
      17: ldc           #10                 // String hello1
      19: invokevirtual #12                 // Method MyGenericList.add:(Ljava/lang/Object;)V
      22: aload_2
      23: iconst_0
      24: invokestatic  #16                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      27: invokevirtual #12                 // Method MyGenericList.add:(Ljava/lang/Object;)V
      30: getstatic     #22                 // Field java/lang/System.out:Ljava/io/PrintStream;
      33: aload_1
      34: iconst_0
      35: invokevirtual #28                 // Method MyGenericList.Get:(I)Ljava/lang/Object;
      38: checkcast     #32                 // class java/lang/String
      41: invokevirtual #34                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      44: getstatic     #22                 // Field java/lang/System.out:Ljava/io/PrintStream;
      47: aload_2
      48: iconst_0
      49: invokevirtual #28                 // Method MyGenericList.Get:(I)Ljava/lang/Object;
      52: invokevirtual #40                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
      55: getstatic     #22                 // Field java/lang/System.out:Ljava/io/PrintStream;
      58: aload_1
      59: invokevirtual #42                 // Method java/lang/Object.getClass:()Ljava/lang/Class;
      62: aload_2
      63: invokevirtual #42                 // Method java/lang/Object.getClass:()Ljava/lang/Class;
      66: if_acmpne     73
      69: iconst_1
      70: goto          74
      73: iconst_0
      74: invokevirtual #46                 // Method java/io/PrintStream.println:(Z)V
      77: return
}

 

通過的MyGenericList類型的字節碼可以看到,兩處invokevirtual的參數確實只有Object類型,而沒有泛型T相關的內容。可以驗證反類型T經過編譯後就是Object。
 
繼續分析Main的字節碼,看到38行的代碼:
38: checkcast #32 // class java/lang/String
其中checkcast是jvm的操作碼,含義是檢查值是否爲給定的類型(此處即爲String類型),如果不是則拋出異常。
至此,可以看到Java泛型是編譯器層面支持的泛型,而jvm層面並不支持泛型。
 
 
基於上述分析,Java這種擦除法的泛型會帶來幾種使用限制:
 
1、T不能是基本類型int\bool等,比如這種就會報錯:類型實參不能爲基元類型
var tt3 = new MyGenericList<boolean>();
2、泛型T得到的Class都是同一個,即:
tt1.getClass() == tt2.getClass() =true

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章