Java 5 引入泛型後,<? extends T> 與 <? super T> 成為 API 設計的核心工具。然而許多開發者對「為何 extends 不能 add、super 不能 get」僅知其一不知其二。本文從**類型理論、編譯器推導、實際 API 設計**三個維度,徹底講透這對關鍵字的本質差異與最佳實踐。
Producer Extends, Consumer Super (PECS)
- 只讀(Producer):用
<? extends T>—— 安全取出T或其子類。- 只寫(Consumer):用
<? super T>—— 安全放入T或其子類。- 又讀又寫:不要用通配符,直接用
<T>。
List<? extends Fruit> —— 協變List<? extends Fruit> list = new ArrayList<Apple>(); // 合法
// list.add(new Apple()); // ❌ 編譯錯誤
Fruit f = list.get(0); // ✅ 安全:無論實際是 Apple/Orange,都是 Fruit
編譯器視角:實際類型可能是 Apple、Orange、Fruit 任一子類。**寫入任何具體類型都可能破壞類型安全**(例如實際是 Orange 卻塞入 Apple),故**禁止所有 add(E) 操作**;但**讀出時統一向上造型為 Fruit 一定安全**。
List<? super Apple> —— 逆變List<? super Apple> list = new ArrayList<Fruit>(); // 合法
list.add(new Apple()); // ✅ 安全:Fruit 能裝 Apple
list.add(new GreenApple()); // ✅ 子類也行
// Fruit f = list.get(0); // ❌ 編譯錯誤
Object o = list.get(0); // ✅ 唯一安全:只能當 Object
編譯器視角:實際類型可能是 Apple、Fruit、Object 任一父類。**讀出時無法確定具體類型**(可能是 Fruit 也可能是 Object),故**只能賦值給 Object**;但**寫入 Apple 或其子類一定安全**(父類引用指向子類物件合法)。
copy(List<? super T> dest, List<? extends T> src)public static <T> void copy(List<? super T> dest, List<? extends T> src) {
for (T t : src) { // src 只讀 → extends
dest.add(t); // dest 只寫 → super
}
}
這是 JDK Collections.copy 的簽名精髓:來源用 extends,目標用 super,實現最大化複用。
Comparator<? super T>public static <T> T max(List<? extends T> list, Comparator<? super T> cmp) {
T max = list.get(0);
for (T t : list) {
if (cmp.compare(t, max) > 0) max = t;
}
return max;
}
? extends TT(比較兩個 T)→ ? super T,支援傳入 Comparator<Object> 比較 String 列表。List<Object> 是 List<String> 父類 | **錯**!泛型**不變**,兩者無繼承關係。 |
| extends 只能用於返回值 | 可用於**參數**(只讀場景),如 void process(List<? extends Dto> list)。 |
| super 只能用於參數 | 可用於**返回值**(工廠模式),如 <T> List<? super T> factory()。 |
| 又要加又要取就用 List<?> | List<?> **只能取 Object、只能放 null**;雙向操作請用 **具體類型參數 <T>**。 |
泛型在編譯期經過**類型擦除**,List<? extends Fruit> 擦除為 List(raw type)。編譯器插入**橋接方法**與**檢查轉型**確保運行期安全:
// 源碼
list.add(new Apple()); // 編譯錯誤
// 擦除後偽代碼(若允許通過)
List raw = list; // 實際是 ArrayList
raw.add(new Apple()); // 運行期拋 ClassCastException
編譯器在編譯期攔截,避免污染堆記憶體,**這就是泛型「擦除但不失安全」的核心機制**。
| 通配符 | 可讀出類型 | 可寫入類型 | 典型場景 |
|---|---|---|---|
<? extends T> | T 及父類 | ❌ (僅 null) | 來源、生產者、只讀遍歷 |
<? super T> | Object 唯一 | T 及子類 | 目標、消費者、收集器、比較器 |
<T> | T | T | 又讀又寫、內部實現、遞歸泛型 |
💡 記憶口訣:
「取用 extends,存用 super」
「Producer Extends, Consumer Super」
下次看到通配符,先問自己:這個集合在當前作用域是「生產者」還是「消費者」?