泛型與反射是 Java 類型系統的雙子星。泛型通過編譯期類型檢查提升安全性,反射則打破編譯期限制實現動態性。本文深度解析《改善 Java 程序的 151 個建議》第 7 章(建議 93-97),揭示泛型擦除本質、通配符 PECS 原則、協變逆變陷阱,助你寫出更健壯的泛型代碼。
Java 泛型僅存在於編譯期,運行期所有泛型信息被擦除為原始類型。這導致三大核心限制:
public void listMethod(List<String> list) {}
public void listMethod(List<Integer> list) {} // 編譯錯誤:擦除後簽名相同
| 泛型類型 | 擦除後類型 |
|---|---|
| List<String>、List<Integer>、List<T> | List |
| List<String>[] | List[] |
| List<? extends E>、List<? super E> | List<E> |
| List<T extends Serializable & Cloneable> | List<Serializable>(取第一個邊界) |
List<String> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
System.out.println(list1.getClass() == list2.getClass()); // true,同一 Class 對象
// 泛型數組初始化非法
// List<String>[] arr = new List<String>[10]; // 編譯錯誤
// instanceof 不能帶泛型參數
// list1 instanceof List<String> // 編譯錯誤
因擦除導致編譯期無法獲取 T 具體類型:
class Test<T> {
// private T t = new T(); // 編譯錯誤
// private T[] arr = new T[5]; // 編譯錯誤
private List<T> list = new ArrayList<>(); // 合法:ArrayList 內部用 Object[]
}
解決方案:通過反射在運行期傳入 Class 對象初始化:
class Test<T> {
private T t;
private T[] arr;
public Test(Class<T> clazz) {
try {
t = clazz.newInstance();
arr = (T[]) Array.newInstance(clazz, 5);
} catch (Exception e) { throw new RuntimeException(e); }
}
}
// 調用:new Test<String>(String.class)
當編譯器無法推斷類型時(如空參數、混合類型),必須显式指定:
// 空參數:默認推斷為 Object
List<Object> list2 = ArrayUtils.asList();
// 強制指定為 Integer
List<Integer> intList = ArrayUtils.<Integer>asList();
// 混合類型:編譯器只推斷 Object,但我們期望 Number
List<Number> numList = ArrayUtils.<Number>asList(1, 2, 3.1);
語法:類名.<類型參數>方法名(),統一輸入輸出類型預期。
核心口訣:Producer Extends, Consumer Super (PECS)
// 從集合讀取元素進行業務處理
public static <E> void read(List<? extends E> list) {
for (E e : list) { // 安全:取出的是 E 或其子類
process(e);
}
}
// 調用:read(Arrays.asList("A", "B")); // E 推斷為 String
此時集合作為生產者,只能取不能存(除 null)。
// 向集合寫入元素
public static <E> void write(List<? super Number> list) {
list.add(123); // Integer 是 Number 子類
list.add(3.14); // Double 是 Number 子類
// list.add(new Object()); // 錯誤:Object 不是 Number 父類
}
此時集合作為消費者,只能存不能安全讀(只能當 Object 讀)。
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
// src 讀取 → extends
// dest 寫入 → super
for (int i = 0; i < src.size(); i++) {
dest.set(i, src.get(i));
}
}
Java 數組支持協變,但泛型不支持:
Number[] nums = new Integer[10]; // 合法:數組協變
// List<Number> list = new ArrayList<Integer>(); // 錯誤:泛型不協變
原因:若允許,可導致類型污染:
List<Number> list = new ArrayList<Integer>();
list.add(3.14); // 運行時放入 Double,但實際是 Integer 列表 → ClassCastException
List<? extends Number> list = new ArrayList<Integer>();(只讀安全)List<? super Integer> list = new ArrayList<Number>();(只寫安全)| 表達式 | 結果 |
|---|---|
| Integer 是 Number 子類型 | ✓ |
| ArrayList<Integer> 是 List<Integer> 子類型 | ✓ |
| Integer[] 是 Number[] 子類型 | ✓ |
| List<Integer> 是 List<Number> 子類型 | ✗ |
| List<Integer> 是 List<? extends Integer> 子類型 | ✗ |
| List<Integer> 是 List<? super Integer> 子類型 | ✗ |
<Type>method()