泛型是 Java 5 引入的里程碑特性,實現了編譯期類型安全與代碼復用的統一。本文系統講解泛型定義、通配符、類型擦除、Class 泛型化、新舊代碼兼容等核心知識點,並剖析底層實現原理。
泛型之前,集合只能存儲 Object,取出需強轉,且無法在編譯期阻擋錯誤類型插入。泛型讓 List 細化為 List<String>、List<Integer> 等不同類型,且 List<String> 不是 List<Object> 的子類型(不支持協變),這保護了類型完整性:
List<String> ls = new ArrayList<>();
// List<Object> lo = ls; // 編譯錯誤!否則可向 lo 插入 Integer 破壞 ls 類型安全
public class Box<T, S extends T> {
private T content;
private S extra;
public T getContent() { return content; }
public void setContent(T content) { this.content = content; }
// 靜態成員不能使用類的類型參數
// private static T staticField; // 編譯錯誤
}
關鍵點:類型參數可用於字段、方法參數、返回值、構造器,**不能用於靜態上下文**(靜態成員屬於類而非實例)。
// 表達多參數與返回值類型關系
public <T, S extends T> T copy(T src, S dest) { ... }
// 僅為多態,優先用通配符
public void printAll(List<?> list) { ... } // 替代 <T> void printAll(List<T> list)
List<String> list = new ArrayList<>(); (鑽石操作符 Java 7+)class MyList<E> extends ArrayList<E> implements List<E>public <T> T first(List<T> list) { return list.get(0); }
Number n = first(Arrays.asList(1, 2, 3)); // T 推導為 Number
Object o = first(Arrays.asList("a", "b")); // T 推導為 Object
// 衝突示例
List<Number> ln = Arrays.asList(1, 2);
List<Integer> li = Arrays.asList(1, 2);
// first(ln, li); // 編譯錯誤:無法同時滿足 T=Number 和 T=Integer
| 通配符形式 | 含義 | 讀取能力 | 寫入能力 |
|---|---|---|---|
<?> |
無界,等價 <? extends Object> |
讀為 Object | 僅能寫 null |
<? extends T> |
上界通配符(協變) | 讀為 T 或其子類 | 僅能寫 null |
<? super T> |
下界通配符(逆變) | 讀為 Object | 可寫 T 或其子類 |
src.copyTo(dest))→ 用 <? extends T>dest.addAll(src))→ 用 <? super T>List<Integer>[] arr = new ArrayList<Integer>[10]; // 編譯錯誤:泛型數組創建非法
// 原因:數組協變 + 類型擦除 = 運行時類型安全漏洞
// 替代方案:List<List<Integer>> listOfLists = new ArrayList<>();
泛型是編譯期技術,運行期不保留具體類型參數(List<String> 和 List<Integer> 運行時都是 List)。
ObjectList<String> l1 = new ArrayList<>();
List<Integer> l2 = new ArrayList<>();
System.out.println(l1.getClass() == l2.getClass()); // true!同一個 Class 對象
instanceof 檢查具體泛型類型:if (list instanceof List<String>) 編譯錯誤unchecked warningnew T[10] 非法Java 5 使 Class 泛型化:String.class 類型為 Class<String>。利用此特性可實現類型安全的反射工廠:
public static <T> Collection<T> select(Class<T> clazz, String sql) {
Collection<T> result = new ArrayList<>();
// JDBC 查詢...
for (Row row : rows) {
T item = clazz.getDeclaredConstructor().newInstance(); // 精確類型
// 映射字段...
result.add(item);
}
return result;
}
// 調用
List<EmpInfo> emps = select(EmpInfo.class, "SELECT * FROM emps");
此技術廣泛應用於註解處理器、依賴注入框架、ORM 映射等場景。
List raw = new ArrayList<String>(); // 警告但合法
List<String> typed = new ArrayList(); // 警告但合法
編譯器允許但發出 unchecked 警告,類型安全由開發者自行保證。
// 庫 v1
class Foo { public Foo create() { return new Foo(); } }
class Bar extends Foo { public Foo create() { return new Bar(); } }
// 庫 v2 升級為協變返回值
class Bar extends Foo { public Bar create() { return new Bar(); } }
若客戶端代碼未重新編譯,調用 bar.create() 會鏈接錯誤。升級庫時需謹慎評估二進制兼容性。
? extends/? super)解決協變/逆變需求,遵循 PECS 原則Class<T> 作為類型令牌突破擦除限制,實現運行期類型傳遞精通泛型,是編寫健壯、可復用、類型安全 Java 代碼的關鍵能力。