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 在編譯相關類時使用了一個橋接方法的機制,通過為實現類新增一個橋接方法,來實現型別擦除後介面的方法。