泛型是 Java 類型系統的參數化能力,將「類型」作為參數傳遞,實現編譯期類型安全與代碼復用的雙贏。本文從泛型類/方法/接口定義、類型擦除原理、通配符與邊界、實戰最佳實踐五個維度,構建完整泛型知識體系。
// 無泛型:Object + 強轉 + 運行期風險
class Box { Object item; void set(Object o) { item = o; } Object get() { return item; } }
Box b = new Box(); b.set("hello"); String s = (String) b.get(); // 運行期才發現類型錯誤
// 有泛型:類型參數化,編譯期保證安全
class Box { T item; void set(T o) { item = o; } T get() { return item; } }
Box box = new Box<>(); box.set("hello"); String s = box.get(); // 無需強轉,編譯期報錯若傳入 Integer
class Pair{ // 多類型參數 private K key; private V value; public Pair(K k, V v) { key = k; value = v; } public K getKey() { return key; } public V getValue() { return value; } } // 實例化時具體化類型參數 Pair p = new Pair<>(1, "one"); int k = p.getKey(); // 直接獲取 Integer,無強轉
// 類型參數聲明在修飾符後、返回類型前 public staticvoid printArray(T[] arr) { for (T e : arr) System.out.print(e + " "); System.out.println(); } // 調用時編譯器自動推導類型參數(鑽石推導) printArray(new Integer[]{1,2,3}); printArray(new String[]{"a","b"});
關鍵區別:泛型方法的類型參數獨立於類,即使在非泛型類中也可定義。
interface Processor{ void process(T t); } class StringProcessor implements Processor { // 實現時固定類型 public void process(String s) { System.out.println("處理: " + s); } } class GenericProcessor implements Processor { // 繼續保持泛型 public void process(T t) { System.out.println("通用: " + t); } }
Java 泛型採用擦除實現:編譯器檢查類型後,字節碼中不保留具體類型參數,全部替換為原始類型或Object。
// 源碼 Listlist = new ArrayList<>(); list.add("hello"); // 字節碼等價於 List list = new ArrayList(); list.add("hello"); // 編譯器插入隱式檢查
new T()、T[] arr = new T[10]、obj instanceof T、T.class。new List[10] → 編譯錯誤(類型安全漏洞)。void m(List) 和 void m(List) 擦除後同簽名。class TypeRef{} // 匿名子類保留類型信息 TypeRef > ref = new TypeRef
>() {}; // 通過反射獲取實際類型參數 ParameterizedType pt = (ParameterizedType) ref.getClass().getGenericSuperclass(); Type[] args = pt.getActualTypeArguments(); // [String]
extends T —— 生產者// 只能讀取 T 或其子類,不能寫入(除 null)
void printAll(List extends Number> nums) {
for (Number n : nums) System.out.println(n.doubleValue());
// nums.add(1); // 編譯錯誤:編譯器不知具體類型,無法保證類型安全
}
printAll(Arrays.asList(1, 2.5, 3L)); // List, List 均可傳入
super T —— 消費者// 只能寫入 T 或其父類,讀取只能作為 Object
void addIntegers(List super Integer> list) {
list.add(10); // OK
list.add(20); // OK
Object o = list.get(0); // 只能作為 Object 讀取
}
List nums = new ArrayList<>();
addIntegers(nums); // List、List
Producer Extends, Consumer Super
只讀 →? extends T;只寫 →? super T;又讀又寫 → 不用通配符(用具體類型)。
// 限制 T 必須是 Number 子類且實現 Comparable public> T max(T a, T b) { return a.compareTo(b) > 0 ? a : b; } // 可調用:max(1, 2), max(1.5, 2.5) // 不可調用:max("a", "b") // String 非 Number 子類
List list = new ArrayList<>(); void print(Collection extends E> c) 最大化靈活性。List 失去類型檢查,僅為兼容舊代碼。@SuppressWarnings("unchecked") 僅在證明安全處加注解。void m(T... args) 可能導致堆污染,JDK 7+ 加 @SafeVarargs 抑制警告。@param 說明 。| 代碼 | 錯誤原因 | 修正 |
|---|---|---|
new T() | 擦除後無構造器信息 | 傳入 Supplier 或工廠 |
T[] arr = new T[10] | 泛型數組創建非法 | 用 List 或 (T[]) new Object[10](慎用) |
obj instanceof List | 運行期無類型參數 | obj instanceof List + 遍歷檢查元素 |
void m(List | 擦除後同簽名 | 合併為 void m(List>) 或重命名 |
class A | 靜態字段不可引用類型參數 | 改為實例字段或泛型方法 |
泛型看似複雜,核心只有三點:編譯期類型檢查、擦除後運行期無痕、通配符控制協變/逆變。熟練掌握 PECS 原則與邊界聲明,能讓你的 API 既安全又極具彈性。