Java泛型筆記

potato_big發表於2020-11-14

泛型的使用可以避免編寫過多的冗餘程式碼,相對於之前的使用Object類來說,更加健壯(型別安全),將型別的強轉放到了編譯期(之前型別的檢查和型別的強轉都必須由程式設計師自己負責),減少了很多由於型別轉換出現的執行時錯誤的出現。

Java中的泛型有兩種應用,一種是泛型類,另外一種是泛型方法

關於泛型的一些內容,可以參考這篇部落格:https://blog.csdn.net/s10461/article/details/53941091 ,總結的比較詳細。

這裡主要想記錄一下關於泛型的擦除機制,事實上,在JVM中是看不到泛型的,所有的泛型在編譯階段就已經被處理成了普通類和方法。 ‘

可以這麼理解:在泛型類或者泛型方法在被編譯器進行編譯的時候,傳入的型別引數被擦除掉,轉而用一個叫“原始型別”的來替換被擦掉的型別資訊。無論我們如何定義一個泛型型別,相應的都會有一個原始型別在編譯的時候被自動提供。

// 擦除前
public class A<T> {
	private T t;
	
	public T getT(){
		return t;
	}
}
// 擦除後
public class A{
	private Object t;
	
	public Object getT(){
		return t;
	}
}

或者可以直接檢視編譯後的位元組碼檔案,可以更加明顯的看出泛型引數T,被替換成Object

// class version 52.0 (52)
// access flags 0x21
// signature <T:Ljava/lang/Object;>Ljava/lang/Object;
// declaration: A<T>
public class A {

  // compiled from: A.java

  // access flags 0x2
  // signature TT;
  // declaration: t extends T
  private Ljava/lang/Object; t

  // access flags 0x1
  public <init>()V
   L0
    LINENUMBER 1 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    RETURN
   L1
    LOCALVARIABLE this LA; L0 L1 0
    // signature LA<TT;>;
    // declaration: this extends A<T>
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x1
  // signature ()TT;
  // declaration: T getT()
  public getT()Ljava/lang/Object;
   L0
    LINENUMBER 5 L0
    ALOAD 0
    GETFIELD A.t : Ljava/lang/Object;
    ARETURN
   L1
    LOCALVARIABLE this LA; L0 L1 0
    // signature LA<TT;>;
    // declaration: this extends A<T>
    MAXSTACK = 1
    MAXLOCALS = 1
}

值得注意的是:如果泛型是使用無限定的方式指定(沒有使用 <T extends Number>等形式),擦除後的原始型別就是Object,但是當使用了限定符後就不是這樣了,例如ArrayList<? extends Number>,擦除的時候是會被替換為限定型別Number,而不是Object。

也正是由於泛型的擦除機制,下面的這段程式碼才會輸出true,這是因為擦除後的ArrayList不帶有泛型資訊,他們的Class都是ArrayList

ArrayList<String> list1 = new ArrayList<>();
ArrayList<Integer> list2 = new ArrayList<>();

System.out.println(list1.getClass() == list2.getClass());
// true
System.out.println(list1.getClass());
// class java.util.ArrayList
System.out.println(list2.getClass());
// class java.util.ArrayList

對於上面的提到的那篇部落格中說的泛型陣列的問題,在明白了型別擦除之後,就很容易搞明白了。

在泛型類的內部,不能直接使用new T()來例項化一個T型別的物件,但是我們可以利用Java的反射機制來獲取實際的型別引數的型別。

可以通過另一個類來繼承這個泛型類,然後使用下面的方式來獲得泛型類中傳入的實際型別的名字,這在Java web中,比如在編寫BaseDao<T>的時候被使用到。

class TestA<T>{
    private Class clazz;

    public TestA(){
        Type type = this.getClass().getGenericSuperclass();
        ParameterizedType parameterizedType = (ParameterizedType) type;
        clazz = (Class) parameterizedType.getActualTypeArguments()[0];
        System.out.println(clazz);
    }
}

class TestB extends TestA<String>{
    public TestB(){
        System.out.println("TestB被呼叫了");
    }
} 

其他由於Java泛型的擦除機制帶來的問題,可以參考《Java核心技術 卷1》中的12.5的相關內容。

相關文章