Java 泛型透過「型別擦除」實現:編譯期進行型別檢查,執行期移除泛型資訊,所有型別參數替換為 Object 或其邊界。這種設計雖保證了向後相容,卻帶來諸多違反直覺的限制。唯有深入理解這些限制,才能真正駕馭泛型程式設計。
// 錯誤
Pair<int> pair = new Pair<int>(1, 100);
// 正確:使用包裝類型
Pair<Integer> pair = new Pair<Integer>(1, 100);
原因:型別參數擦除為 Object,基本型別非物件。
Pair<Integer> p1 = new Pair<>(1, 2);
Pair<String> p2 = new Pair<>("a", "b");
// 以下皆編譯錯誤
// Pair<Integer>.class // 無法獲取具體泛型 Class
// p1 instanceof Pair<Integer> // instanceof 不支援泛型
// 僅支援原始型別或通配符
System.out.println(Pair.class == p1.getClass()); // true
System.out.println(p1 instanceof Pair<?>); // true
衝突一:介面重複實作
class Base implements Comparable<Base> {}
class Child extends Base implements Comparable<Child> {} // 編譯錯誤!
擦除後皆為 Comparable,視為重複實作。解決:子類別重寫父類別方法。
衝突二:方法多載失效
// 編譯錯誤:擦除後簽名相同
void test(DynamicArray<Integer> arr) {}
void test(DynamicArray<String> arr) {}
// 皆編譯錯誤
T obj = new T();
T[] arr = new T[10];
解決方案:傳入 Class<T> 利用反射建立:
public static <T> T create(Class<T> type) {
try { return type.newInstance(); }
catch (Exception e) { return null; }
}
// 使用:Date d = create(Date.class);
// 錯誤
class Singleton<T> {
private static T instance; // 編譯錯誤
public static T getInstance() {} // 編譯錯誤
}
原因:靜態成員屬於類別,與型別參數無關。但靜態方法可定義自己的型別參數(泛型方法)。
// 語法:T extends Base & Interface1 & Interface2
// 類別邊界必須在第一位,擦除時使用第一個邊界
class Example<T extends Base & Comparable & Serializable> {}
// 編譯錯誤
Pair<String, Integer>[] arr = new Pair<String, Integer>[3];
根本原因:陣列是協變的,泛型是不變的。允許泛型陣列會導致型別安全漏洞:
// 假設允許建立泛型陣列
Pair<Object, Integer>[] options = new Pair<Object, Integer>[3];
Object[] objs = options; // 陣列協變,合法
objs[0] = new Pair<Double, String>(12.34, "hello"); // 編譯/執行皆通過!
// 但實際型別不匹配,後續存取必然拋出異常
Pair[] arr = new Pair[3];(失去型別檢查)List<Pair<String, Integer>> list = new ArrayList<>();// 錯誤:Object[] 無法轉型為 Integer[]
public E[] toArray() {
return (E[]) Arrays.copyOf(elementData, size); // ClassCastException
}
// 正確:傳入 Class<E> 利用反射建立正確型別陣列
public E[] toArray(Class<E> type) {
E[] arr = (E[]) Array.newInstance(type, size);
System.arraycopy(elementData, 0, arr, 0, size);
return arr;
}
// 使用:Integer[] arr = list.toArray(Integer.class);
Object[],轉型為具體型別陣列需反射Java 泛型的限制本質上源於「型別擦除」與「向後相容」的設計取捨。幸運的是,現代 IDE 與編譯器會在違反規則時給予明確提示。開發者只需理解背後原理,遇到錯誤時能從容應對。掌握這些限制,是進階為 Java 架構師的必修課。