我理解的 Java 泛型

Generic Type

generic type && type parameterA generic type is a type with formal type parameters.以 interface List {} 為例, List 是 generic type, E 是 type parameter.parameterized type && type argumentA pameterized type is an instantation of a generic type with type argument.以 List<string> stringList; 為例, List<string> 是 parameterized type, 是 List 的一個實例, String 是 type argument./<string>/<string>

Wildcard

泛型中將通配符(wildcard)分為三類:

? - unbound, 不做任何限定的通配符.? extends Number - upper bounded, 限定必須是 Number 或其子類.? super Integer - lower bounded, 限定必須是 Integer 或其父類.

後兩者也被統稱為 bounded wildcard.

結合通配符, parameterized type 也可以劃分為三類.

conceret type argument
generic type ---------------------------> conceret parameterized type
unbound type argument
generic type ---------------------------> unbound parameterized type
bounded type argument
generic 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;
}
}
/<object>/<integer>/<integer>/<number>/<number>/<number>

理論上, 泛型相關的繼承關係判斷需要從兩個緯度考慮:

generic type 之間是否有繼承關係type argument 之間是否有超集關係

具體而言. 對於 type argument 相同的情況, generic type 之間的繼承關係決定兩個 parameterized type 的父子關係, 所以 List<number> 是 ArrayList<number> 的父類./<number>/<number>

但 ArrayList<number> 不是 ArrayList<integer> 的父類, type argument 不同的情況下, 泛型之間的繼承關係判斷會很複雜. 主要是由於 wildcard 的存在, 導致 type argument 可以代表一類類型, 所以要引入集合中的超集(superset)概念, 即一方所代表的所有類型完全包含在以一方內./<integer>/<number>

最終的判斷標準是, 在 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
/<string>/<string>

究其根本, 是因為數據的組成元素都應該是同一類型的: 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>();
/<integer>/<string>/<string>/<string>

關注我:私信回覆“555”獲取往期Java高級架構資料、源碼、筆記、視頻Dubbo、Redis、Netty、zookeeper、Spring cloud、分佈式、高併發等架構技術往期架構視頻截圖