Java 集合在無泛型時代默認存 Object,導致兩大痛點:
ClassCastException(編譯期不報錯,運行期崩潰)。// 無泛型隱患
List list = new ArrayList();
list.add("hello");
list.add(100); // 允許混入 Integer
String s = (String) list.get(1); // 運行時拋 ClassCastException
List<String> 將類型參數化,String 為類型實參,接口定義中的 E 為類型形參。編譯器據此:
list.add(100)(編譯報錯)。get() 直接返回 String,無需轉型。public class ArrayList<E> ... {
transient Object[] elementData; // 底層仍是 Object 數組
public boolean add(E e) {
elementData[size++] = e; // 存入時無類型檢查(擦除後)
return true;
}
public E get(int index) {
return (E) elementData[index]; // 取出時編譯器插入轉型指令
}
}
泛型信息僅存在編譯期,字節碼中被擦除為原始類型(Object),這就是「類型擦除」。
// 泛型類
class Box<T> {
private T data;
public Box(T data) { this.data = data; }
public T getData() { return data; }
}
// 泛型方法(類不泛型,方法泛型)
public static <T> void print(T t) { System.out.println(t); }
關鍵結論:Box<String> 和 Box<Integer> 在運行期是同一個 Class 對象(Box.class),邏輯上視為不同類型,物理上共享字節碼。
泛型不具備多態性:Box<Number> 不是 Box<Integer> 的父類。為解決「同時接受 Number 及其子類」需求,引入通配符 ?:
// 無界通配符:邏輯父類
void print(Box<?> box) { ... }
// 上界:只接受 Number 及子類
void printNumber(Box<? extends Number> box) { ... }
// 下界:只接受 Number 及父類
void addNumber(Box<? super Number> box) { ... }
extends:讀取安全(取出必是 Number),寫入受限(只能寫 null)。super:寫入安全(可寫 Number 及子類),讀取只能作 Object。Producer Extends, Consumer Super
? extends T。? super T。new List<String>[10] 編譯錯誤,改用 List<List<String>>。static T field 非法。class MyEx<T> extends Exception 非法。new T() 需借助反射或工廠模式。泛型將類型檢查前置到編譯期,消除運行期轉型風險,配合通配符上下界實現靈活的 API 設計。理解「擦除機制」與「PECS 原則」是駕馭 Java 泛型的核心鑰匙。