Java的泛型機制

pedro7發表於2022-02-22

Java的泛型機制

泛型是 Java 從 JDK5 開始引入的新特性,本質上是引數化型別,即所操作的資料型別被指定為一個引數。這意味著編寫的程式碼可以被很多不同型別的物件所重用。

1. 泛型的使用方式

1.1 泛型類

用下面的語法可以定義一個泛型類:

class C< T, E, ...>{
    private T t;
    ...
}

常用的泛型標識有 T、E、K、V。

用下面的語法可以建立一個泛型物件:

C<具體的資料型別> c = new C<>();

泛型類有以下注意事項:

  • 如果沒有指定具體的資料型別,操作型別是 Object。
  • 泛型的型別引數只能是類型別,不能是基本資料型別。
  • 泛型型別在邏輯上看作多個不同型別,但實際上是相同型別。

用下面的語法可以從泛型類派生子類:

// 子類也是泛型類,要和父類的泛型型別保持一致。但可以新增更多型別。
class Child<T> extends Father<T>
class Child<T, E, K> extends Father<T>  
// 子類不是泛型類,父類要明確一個泛型的資料型別
class Child extends Father<String>

1.2 泛型介面

定義方式類似於泛型類。

當需要用一個類實現泛型介面時:

  • 如果實現類不是泛型類,介面要明確資料型別。
  • 如果實現類是泛型類,實現類和介面的泛型型別要一致,但也可以增加更多。

1.3 泛型方法

用下面的語法可以定義一個泛型方法:

public <T, E, ...> void f(){
    ...
}

泛型類的型別由構造物件時決定,泛型方法的型別由呼叫方法時決定。

1.4 型別萬用字元

我們用?作為型別萬用字元,代表具體的型別實參。

使用extends語句可以代表型別萬用字元的上限:

類/介面<? extends 實參型別>

要求該泛型的型別,只能是實參型別或者實參型別的子類型別。

使用super語句可以代表型別萬用字元的下限:

類/介面<? super 實參型別>

帶有超型別限定的萬用字元可以向泛型物件寫入,帶有子型別限定的萬用字元可以從泛型物件讀取。

2. 型別擦除式泛型

Java 的泛型實現方式是型別擦除的偽泛型。在 Java 中,泛型只在程式原始碼中存在,編譯後的位元組碼檔案中泛型全部被擦除,替換為原來的裸型別,並在相應的位置插入了強制轉型程式碼。

假設我們有下面這段程式碼:

public static void main(String[] args){
	Map<String, String> map = new HashMap<String, String>();
    map.put("hello", "你好");
    System.out.pirntln(map.get("hello"));
}

這裡向泛型型別為<Stirng, String>的 map 內插入了一個鍵值對,又從中將值取出。如果我們先將這段 Java 程式碼編譯為 Class 檔案,又反編譯 Class 檔案,實際上會得到以下程式碼:

public static void main(String[] args){
	Map map = new HashMap();
    map.put("hello", "你好");
    System.out.pirntln((String)map.get("hello"));
}

顯然,在前端編譯過程中,物件的泛型型別被擦除,轉換為了沒有泛型的裸型別。而在位元組碼檔案的相關位置插入了強制型別轉換程式碼,從而實現泛型。

型別擦除式的泛型帶來了幾個嚴重的問題:

Ⅰ. 不支援基礎資料型別的泛型

由於我們無法在 int、long 等基礎資料型別和 Object 之間強制轉型,所以 Java 的泛型不支援基礎資料型別。

Ⅱ. 執行時無法獲取泛型型別資訊

加入我們想寫一個泛型版本的 List 轉換為陣列的方法,由於不能再執行時獲取泛型資訊,只能再傳入一個元素的型別。

public static <T> T[] convert(List<T> list, Class<T> componentType){
    Tp[] array = (T[])Array.newInstance(componentType, list.size());
}

Ⅲ. 無法正常的實現過載等功能

例如兩個方法我們試圖依賴泛型型別不同來實現過載,就會發生編譯錯誤。因為泛型型別在前端編譯器被擦除了,變成了兩個一模一樣的方法。

3. 橋接方法的機制

當一個類實現一個泛型介面時,泛型介面在編譯後型別被擦除,這樣我們在實現類中就不能找到對應介面的實現方法。為了解決這個問題,Java 在編譯相關類時使用了一個橋接方法的機制,通過為實現類新增一個橋接方法,來實現型別擦除後介面的方法。

image-20220222114826866

相關文章