先說結論 , Java 的泛型是偽泛型 , 在運行期間不存在泛型的概念 , 泛型在 Java 中是 編譯檢查 + 運行強轉 實現的
泛型是指 允許在定義類 , 接口和方法時使用的類型參數 , 使得代碼可以在不指定具體類型的情況下操作不同的數據類型 , 從而實現類型安全的代碼復用 的語言機制 .
集合框架和內置函數式接口等內容都使用了泛型 .
通配符
<>
是泛型類型參數列表 , 可以包含多個參數 .
?
是通配符 , 可以配合 extends 和 super 關鍵字表示不明類型的上下限 .
<T>
通常用于泛型類源碼聲明 , T 作為占位符表示實例化的泛型參數 .
<?>
可以用在實例化上 , 此時泛型參數不明確 , 通常不能寫入變量 .
不能 Box<?> box = new Box<?>();
, 但可以聲明 Box<?> box = new Box<String>();
, 這樣只能安全讀取 Object 元素 , 不能寫入任何非 null 元素 . new 語句右側的 <> 叫做鉆石操作符 , 鉆石操作符的作用是讓編譯器根據左側泛型參數推斷右側的實際參數 . 如果左側是 <?> 參數不明 , 那么右側則必須顯式指定泛型參數 .
通配符寫法 | 作用 | 允許讀取 | 允許寫入 |
---|---|---|---|
<?> | 任意類型 | 可以讀取為 Object | 只能寫入 null |
<? extends T> | T 或其子類型(上界) | 可以讀取為 T | 不能寫入具體元素 |
<? super T> | T 或其父類型(下界) | 只能讀取為 Object | 可以寫入 T 或子類型 |
生產消費原則 ( PECS )
Producer Extends, Consumer Super .
- 生產者使用
<? extends T>
上界通配符 : 只能讀 , 不能寫 .
List<? extends Number> list = new ArrayList<Integer>();
list 是 Number 或其子類類型的集合 , 可以安全地將集合元素當作 Number 讀取 , 因為確定是 Number 及其子類 .
只能確定泛型參數的上界 , 實際類型不確定 , 所以不允許寫入 .
- 消費者使用
<? super T>
下界通配符 : 只能寫 , 讀不明白 .
List<? super Integer> list = new ArrayList<Number>();
list 是 Integer 或其父類類型的集合 , 因為 Java 向下轉型是安全的 , 所以可以將 Integer 或其子類放入集合 , 因為集合元素類型一定是 Integer 或其父類 .
因為只能確定泛型參數的下界 , 所以編譯期將 ? super Integer
擦除成 Object , 且讀取時只能保證元素是 Object .
類型擦除
編譯器將泛型類型替換成原始類型以保證向后兼容性 ( 兼容不支持泛型的舊版本 Java ) , 向類 , 接口或方法傳遞的泛型類型參數 類名<類型>
只在編譯期存在 , 編譯后泛型信息會被擦除 , 生成的字節碼文件不包含具體的泛型類型 .
類型參數被擦除為它的第一個邊界類型 : 如果是 T
, 則擦除為祖宗類 Object , 如果是 T extends 父類
, 則擦除為父類 . 集合中 List<T>
擦除為 List
.
List<String> list = new ArrayList<>();
List.add("Hello");
String s = List.get(0);👇
(在字節碼中類似于)
List list = new ArrayList();
list.add("Hello");
String s = (String)list.get(0);
instanceof 關鍵字用于判斷對象是否是某個類或其子類的實例 , a instanceof b 作為語句返回布氏值 .
Tips : 基本數據類型不具備繼承體系 , 也不是對象 , 不能被泛型的擦除機制處理 , 所以泛型必須使用基本數據類型的包裝類 .
類型擦除會導致
-
運行時不知道泛型類型 .
List<String> a = new ArrayList<>(); List<Integer> b = new ArrayList<>(); a.equals(b); // true a.getClass() == b.getClass() // true
-
泛型不能用于靜態字段 . 因為不同泛型實例在運行時本質上是同一個類 , 它們會共享同一個靜態字段 , 如果這個靜態字段是泛型參數
T
的 , 那么就會出現實例類型沖突 ( 靜態字段只有一份 , 不同實例期望的類型卻不同 ) . -
不能用 instanceof 關鍵字判別泛型類 . 因為不同的泛型類在運行時被擦除為同一個類 , 此時再使用 instanceof 關鍵字沒有任何意義 .
編譯期類型檢查
仍然以集合為例 , 編譯期完成的任務有 :
-
檢測傳入泛型集合的字段類型是否正確 .
List<String> list = new ArrayList<>(); list.add("Hello"); list.add(123); // 編譯錯誤, int 不是 String
-
取出泛型集合對象時是否能正確映射 .
int get = list.get(0); // 編譯錯誤, 不能把 String 當作 int
類型檢查本質是繼承鏈的轉換關系 , 前面提到的向上轉型機制在通配符 , 類型擦除和類型檢查等特性上得到充分實現 .