泛型 是 Java 5 引入的參數化類型機制,允許在定義類、接口、方法時使用「類型參數」佔位,實際使用時再指定具體類型。其核心動機是解決集合框架中「只能存 Object、取出需強轉、運行時才發現類型錯誤」的三大痛點。
// 無泛型:不安全、需強轉、碼頭費
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0); // 運行時才報 ClassCastException
// 有泛型:編譯期類型安全、無強轉、可讀性強
List<String> list = new ArrayList<>();
list.add("hello");
// list.add(123); // 編譯報錯!
String s = list.get(0); // 無需強轉
public class Box<T> { // T 為類型參數
private T value;
public void set(T value) { this.value = value; }
public T get() { return value; }
}
// 使用:指定實際類型參數
Box<Integer> intBox = new Box<>();
Box<String> strBox = new Box<>();
| 字母 | 含義 | 典型場景 |
|---|---|---|
| T | Type | 通用類型 |
| E | Element | 集合元素 |
| K/V | Key/Value | Map 鍵值 |
| N | Number | 數值類型 |
| R | Result | 返回類型 |
方法級別的類型參數化,獨立於類:
public <T> T firstElement(List<T> list) {
return list.isEmpty() ? null : list.get(0);
}
// 調用:編譯器自動推斷 T
String s = firstElement(Arrays.asList("a", "b"));
Integer i = firstElement(List.of(1, 2));
// 上界通配符:只能讀,不能寫 (生產者)
public void printList(List<? extends Number> list) {
for (Number n : list) System.out.println(n);
}
// 下界通配符:只能寫,讀出只能是 Object (消費者)
public void addIntegers(List<? super Integer> list) {
list.add(1); // OK
// Number n = list.get(0); // 錯誤:只能賦值給 Object
}
// 無界通配符:僅操作與類型無關的方法 (size, clear, isEmpty)
public void printSize(List<?> list) {
System.out.println(list.size());
}
<T extends Number > // T 必須是 Number 或其子類
<T extends Comparable<T>> // T 實現了自比較
<T extends Serializable & Cloneable> // 多重界限
Java 泛型採用類型擦除實現:編譯器編譯時檢查類型,生成的字節碼中不包含泛型信息,所有類型參數被替換為其邊界類型 (默認 Object) 或原始類型。
new T()、new T[10] 非法。clazz.newInstance() 或傳入 Class<T> 工廠。<T> static void method(T t)。list instanceof List<String> 非法。list instanceof List (原始類型)。new List<String>[10] 非法 (類型安全漏洞)。List<List<String>> 或 @SafeVarargs。class MyEx<T> extends Exception 非法。interface Collection<E> {
boolean addAll(Collection<? extends E> c);
}
允許 Collection<Integer> 添加到 Collection<Number>,體現「生產者用 extends」原則 (PECS)。
public interface Comparable<T> {
int compareTo(T other);
}
// 正確實現:String implements Comparable<String>
public class String implements Comparable<String> { ... }
// 通用最大值方法
public static <T extends Comparable<T>> T max(T a, T b) {
return a.compareTo(b) > 0 ? a : b;
}
// 獲取泛型實際類型的經典手法 (如 Hibernate DAO)
public abstract class BaseDao<T> {
private Class<T> entityClass;
@SuppressWarnings("unchecked")
public BaseDao() {
// 通過反射獲取子類聲明的泛型參數
this.entityClass = (Class<T>) ((ParameterizedType) getClass()
.getGenericSuperclass()).getActualTypeArguments()[0];
}
public T get(Serializable id) {
return entityManager.find(entityClass, id); // 類型安全!
}
}
// 子類無需重寫 getEntityClass()
public class UserDao extends BaseDao<User> { }
// 數組是協變的:Integer[] 是 Number[] 的子類
Number[] nums = new Integer[10];
nums[0] = 3.14; // 運行時拋 ArrayStoreException
// 泛型是不變的:List<Integer> 不是 List<Number> 的子類
List<Number> list = new ArrayList<Integer>(); // 編譯錯誤!
// 否則可導致:
// list.add(3.14); // 污染了 Integer 列表
// Integer i = intList.get(0); // ClassCastException
這是為類型安全犧牲靈活性的經典權衡。
List<?> 僅用於類型無關操作。@param <T> 必須實現 Serializable。Java 泛型是「編譯期類型安全」與「運行期類型擦除」的精妙平衡。深入理解擦除機制、通配符協變/逆變、PECS 原則、類型令牌模式,是從「會用泛型」進階到「精通泛型設計」的必經之路。合理使用泛型,能讓你的 API 既具備強類型安全,又保持極高的復用靈活性。