前言
Java 泛型是編譯期類型系統的擴展,通過**參數化類型**實現代碼複用與類型安全。本文系統剖析**泛型類/方法定義、通配符上下界 (PECS)、類型擦除本質與後果、Class<T> 泛型化反射工廠、陣列與泛型的不兼容、最佳實踐模式**,助你從「會用」進化到「懂原理、能設計 API、避坑」。
一、泛型定義語法與規則
1. 泛型類
// 多類型參數、上界限定
public class Pair> {
private final K key;
private final V value;
public Pair(K key, V value) { this.key = key; this.value = value; }
public K getKey() { return key; }
public V getValue() { return value; }
}
// 靜態成員不可使用類的類型參數
// private static T single; // ❌ Compile Error
// public static T get() {} // ❌
2. 泛型方法
public class Util {
// 類型參數在修飾符後、返回類型前
public static T first(List list) {
return list.isEmpty() ? null : list.get(0);
}
// 多參數、上界、返回類型關聯
public static > T max(T a, T b) {
return a.compareTo(b) > 0 ? a : b;
}
// 通配符參數:只讀用 extends,只寫用 super
public static void copy(List super T> dest, List extends T> src) {
for (T t : src) dest.add(t);
}
}
3. 類型參數命名慣例
| 字母 | 含義 | 典型場景 |
| T | Type | 通用類型參數 |
| E | Element | 集合元素類型 |
| K/V | Key/Value | Map 鍵值 |
| R | Return | 返回類型 |
| S/U/V | 第 2/3/4 類型參數 | 多參數泛型方法 |
二、通配符與 PECS 原則深度解析
1. 三種通配符形式
List> // 無界通配符:只能讀 Object、只能寫 null
List extends Number> // 上界通配符:協變,只能讀 Number 及父類、不能寫 (除 null)
List super Integer> // 下界通配符:逆變,只能寫 Integer 及子類、讀只能得 Object
2. PECS 實戰:API 設計決策表
| 場景 | 角色 | 簽名 | 理由 |
| 集合作為數據來源 | Producer | `void process(List extends T> source)` | 支援子類列表,安全讀取 |
| 集合作為數據目標 | Consumer | `void collect(List super T> target)` | 支援父類列表,安全寫入 |
| 既要讀又要寫 | - | `void transfer(List list)` | **不用通配符**,用具體類型參數 |
| 比較器 | Consumer | `Comparator super T>` | 可用父類比較器比較子類對象 |
| 工廠/生產者 | Producer | `Supplier extends T>` | 可返回子類實例 |
3. 類型推導實例
List ints = List.of(1,2,3);
List nums = new ArrayList<>();
// copy(List super T> dest, List extends T> src)
// T 推導為 Integer
Util.copy(nums, ints); // ✅ nums 是 List,Number 是 Integer 父類 → ? super Integer
// Util.copy(ints, nums); // ❌ ints 是 List,不是 ? super Number
三、類型擦除:本質、後果與補償
1. 擦除規則
- **無界擦除至 Object**:
List → List (raw type)。
- **有界擦除至首個界限**:
T extends Number & Comparable → Number。
- **擦除後插入強制轉型**:編譯器在調用處自動插入
checkcast。
2. 經典後果與對策
| 現象 | 原因 | 對策 |
| `new T()` ❌ | 運行期無類型信息 | 用 `Supplier`、`Class`、反射 `clazz.getDeclaredConstructor().newInstance()` |
| `T[] arr = new T[10]` ❌ | 陣列需具體組件類型 | 用 `List`、`(T[]) new Object[10]` (不安全)、`Array.newInstance(clazz, 10)` |
| `instanceof T` ❌ | 運行期 T 已擦除 | 用 `clazz.isInstance(obj)`、`obj.getClass() == clazz` |
| 泛型陣列 `new List[10]` ❌ | 類型安全漏洞 (heap pollution) | 用 `List>`、或 `@SafeVarargs` 方法參數 |
| 重載消失 `void m(List)` / `void m(List)` | 擦除後同簽名 | 改方法名、或用不同參數位置區分 |
| 靜態成員共享 | 擦除後單一 Class 物件 | 靜態域/方法勿用類型參數 |
3. 橋接方法
// 源碼
class MyList implements List {
public void add(T t) { ... }
}
// 編譯器生成橋接方法 (字節碼)
public void add(Object o) { // 擦除後簽名
add((T) o); // 調用真實方法,插入 checkcast
}
四、Class<T> 泛型化與反射工廠模式
1. Class<T> 語義
- `String.class` 型別為 `Class`
- `List.class` 型別為 `Class
` (raw type)
- `new ArrayList().getClass()` → `Class extends ArrayList>` (運行期擦除)
2. 類型安全反射工廠
public interface Factory {
T create();
}
public class ReflectionFactory implements Factory {
private final Class clazz;
public ReflectionFactory(Class clazz) { this.clazz = clazz; }
@Override
public T create() {
try {
return clazz.getDeclaredConstructor().newInstance();
} catch (ReflectiveOperationException e) {
throw new IllegalStateException("Cannot instantiate " + clazz, e);
}
}
}
// 使用:類型安全、無需強轉
Factory userFactory = new ReflectionFactory<>(User.class);
User u = userFactory.create(); // 型別為 User,非 Object
3. JDBC 查詢映射實戰
public List query(String sql, Class rowType, Object... params) {
List results = new ArrayList<>();
try (Connection c = ds.getConnection();
PreparedStatement ps = c.prepareStatement(sql)) {
for (int i = 0; i < params.length; i++) ps.setObject(i+1, params[i]);
try (ResultSet rs = ps.executeQuery()) {
// 利用 Class 獲取構造函數/字段,構建 T 實例
Constructor ctor = rowType.getDeclaredConstructor();
while (rs.next()) {
T obj = ctor.newInstance();
// 反射/Setter/Mapper 填充字段
mapRow(rs, obj);
results.add(obj);
}
}
} catch (SQLException | ReflectiveOperationException e) {
throw new DataAccessException(e);
}
return results;
}
// 調用
List users = dao.query("SELECT * FROM users WHERE age > ?", User.class, 18);
五、陣列與泛型:核心不兼容性
// ❌ 編譯錯誤:泛型陣列創建非法
List[] arr = new List[10];
// ✅ 合法:原始類型陣列 + 無檢查轉型 (開發者自擔風險)
@SuppressWarnings("unchecked")
List[] arr = (List[]) new List>[10]; // 或 new List[10]
// 原因:陣列協變 + 泛型不變 = 類型安全漏洞
Object[] objArr = new String[10];
objArr[0] = 123; // 運行期 ArrayStoreException
// 若允許泛型陣列
List[] strLists = new List[10];
Object[] objLists = strLists; // 陣列協變,合法
objLists[0] = new ArrayList(); // 運行期無異常!
strLists[0].add("hello"); // 向 List 加 String → 後續讀取 ClassCastException
六、最佳實踐清單
- API 邊界優先用泛型:集合、工具類、DAO、Service 接口。
- 通配符遵循 PECS:只讀 `extends`、只寫 `super`、雙向不用通配符。
- 避免泛型陣列:用 `List
>`、`Map>`。
- 反射/工廠用 `Class` 傳遞類型信息:保留運行期類型。
- 文檔化類型參數約束:`@param must implement Comparable`。
- 啟用編譯器警告:`-Xlint:unchecked`、`@SuppressWarnings("unchecked")` 精確標註。
- 不要為了泛型而泛型:單一具體類型場景直接用具體類別。
💡 核心心法:泛型是編譯期遊戲,擦除後只剩 Object。設計 API 時:**生產者用 `extends`,消費者用 `super`,需要運行期類型傳 `Class`,陣列別用泛型**。