前言
在學習 Spring 的依賴注入時, 被 Google 導流到了 Java Generics FAQs. 這篇文章深入講解了 Java 中泛型相關的方方面面, 閱讀完畢後, 整理了自己的一些理解.
概念與名詞
在進入具體的討論之前, 我們需要先明確幾個名詞的含義.
Generic Type
-
generic type &
&
type parameter
A generic type is a type with formal type parameters.
以interface List<
為例,
E>
{
}List<
是 generic type,
E>E
是 type parameter. -
parameterized type &
&
type argument
A pameterized type is an instantation of a generic type with type argument.
以List<
為例,
String>
stringList;List<
是 parameterized type, 是
String>List<
的一個例項,
E>String
是 type argument.
Wildcard
泛型中將萬用字元(wildcard)分為三類:
- ? – unbound, 不做任何限定的萬用字元.
- ? extends Number – upper bounded, 限定必須是 Number 或其子類.
- ? super Integer – lower bounded, 限定必須是 Integer 或其父類.
後兩者也被統稱為 bounded wildcard.
結合萬用字元, parameterized type 也可以劃分為三類.
conceret type argumentgeneric type --------------------------->
conceret parameterized type unbound type argumentgeneric type --------------------------->
unbound parameterized type bounded type argumentgeneric type --------------------------->
bounded parameterized type複製程式碼
Raw Type
Raw type 的存在是為了相容引入泛型之前的版本, 大多數時候你都可以不用考慮它.
Type Erasure
嚴格說來, 泛型只存在於編譯期間, JVM 並不感知泛型. 在編譯時, 編譯器通過 type erasure 來消除 type parameter 和 type argument.
具體的處理方式是:
- Generic type 中的 type parameter 都會被其上確界(leftmost bound)所代替.
- Parameterized type 中的 type argument 被直接移除, parameterized type 轉變為對應的 raw type.
何為上確界, 對於 upper bounded type paramter 而言, 是指其公共父類, <
? extends Number>
對應的就是 Number.
對於其他型別的 type paramter 而言, 因為 type argument 只能是引用型別(reference type), 而引用型別的公共父類是 Object, 所以其上確界都是 Object.
我們可以從 Java Generics FAQs 的例子中看到具體的轉換過程. 左邊是原始的程式碼, 右邊是經過 type erasure 轉換後的結果.
這個例子同時也反應了, 在 type erasure 的過程中, 編譯器可能會按需加入 bridge method 和 type cast.
泛型的型別系統
泛型的引入使得物件之間的繼承關係變得更復雜, 如下這個例子中的一部分就是錯誤的.
public class SuperDemo {
public static void main(String args[]) {
List<
Number>
a = new ArrayList<
Number>
();
ArrayList<
Number>
b = new ArrayList<
Integer>
();
List<
? extends Number>
c = new ArrayList<
Integer>
();
List<
? super Number>
d = new ArrayList<
Object>
();
List<
? super Integer>
e = d;
}
}複製程式碼
理論上, 泛型相關的繼承關係判斷需要從兩個緯度考慮:
- generic type 之間是否有繼承關係
- type argument 之間是否有超集關係
具體而言. 對於 type argument 相同的情況, generic type 之間的繼承關係決定兩個 parameterized type 的父子關係, 所以 List<
是
Number>ArrayList<
的父類.
Number>
但 ArrayList<
不是
Number>ArrayList<
的父類, type argument 不同的情況下, 泛型之間的繼承關係判斷會很複雜. 主要是由於 wildcard 的存在, 導致 type argument 可以代表一類型別, 所以要引入集合中的超集(superset)概念, 即一方所代表的所有型別完全包含在以一方內.
Integer>
最終的判斷標準是, 在 type argument 不相同的情況下, 如果 type argument 是對方的超集, 而且 generic type 與對方相同或者是對方的父類, 那麼當前的 parameterized type 才是對方的父類.
這時候再來回答以下的問題就會比較簡單了:
- How do instantiations of a generic type relate to instantiations of other generic types that have the same type argument?
- How do unbounded wildcard instantiations of a generic type relate to other instantiations of the same generic type?
- How do wildcard instantiations with an upper bound relate to other instantiations of the same generic type?
- How do wildcard instantiations with a lower bound relate to other instantiations of the same generic type?
泛型的特殊使用姿勢
觀察泛型在異常處理和陣列中的使用限制, 思考是什麼導致了這些限制, 在一定程度上可以驗證自己之前的理解是否正確.
泛型與異常
Java 的異常處理在執行時生效, 而 type erasure 發生在編譯期間, 所以大多數時候, 泛型在異常處理中並沒有用武之地.
- Java 不允許任何
Throwable
的子類是泛型. 這主要是由於 type erasure 導致 catch 不能在執行時區分一個 generic type 的不同例項, 所以把 error 或者 exception 定義為泛型不具有任何實際意義. - 同樣由於 type erasure, catch 語句也不接受 type parameter.
- throws 語句可以接受 type parameter, 編譯器在編譯時其會將 type parameter 的替換為具體的異常.
- 你可以 throw 型別為 type parameter 的異常, 但實際上你基本沒有機會這麼做, 因為我們無法新建一個型別為 type parameter 的物件.
泛型與陣列
與異常處理類似, 陣列在執行時儲存了每個元素的型別資訊, 所以泛型陣列也是一個沒有太大意義的概念. 雖然可以定義一個資料的元素為泛型, 但我們僅能新建元素為 unbound parameterized type 的泛型陣列. 具體而言, 下例子中 line 1 和 line 2 合法, 但 line 3 是錯誤的.
List<
String>
[] a;
List<
?>
[] b = new List<
?>
[10];
a = new List<
String>
[10];
// error複製程式碼
究其根本, 是因為資料的組成元素都應該是同一型別的: An array is a container object that holds a fixed number of values of a single type. 而同一 generic type 對應的不同例項實質上並不等價, 但經過 type erasure 後, 並不能在執行時區分出這些.
假如能新建元素為 concerete parameterized type 的陣列, 考慮如下案例.
List<
String>
[] stringLists = new List<
String>
[10];
stringLists[0] = new List<
String>
();
stringLists[1] = new List<
Integer>
();
複製程式碼