J2SE 5.0 引入泛型,實現類型參數化,將類型檢查從運行時前移到編譯期,徹底解決集合框架「只能存 Object、取出需強轉、運行時才發現類型錯誤」的痛點。
// 最簡單泛型類
public class Box { }
// 實用泛型類:類型安全的列表
public class GenList {
private T[] elements;
private int size = 0;
@SuppressWarnings("unchecked")
public GenList(int capacity) {
elements = (T[]) new Object[capacity]; // 必須轉型,無法 new T[]
}
public void add(T e) {
if (size < elements.length) elements[size++] = e;
}
public T get(int index) {
if (index >= 0 && index < size) return elements[index];
return null;
}
}
public interface Processor {
void process(T item);
}
// 多類型參數
public interface Map {
V get(K key);
V put(K key, V value);
}
// 靜態工具方法常用泛型方法
public static void swap(T[] array, int i, int j) {
T temp = array[i];
array[i] = array[j];
array[j] = temp;
}
// 限定類型參數上界
public static > T max(T[] array) {
T max = array[0];
for (T e : array) if (e.compareTo(max) > 0) max = e;
return max;
}
// 上界:T 必須是 Number 或其子類
public class NumberBox {
private T value;
public void set(T value) { this.value = value; }
public double doubleValue() { return value.doubleValue(); }
}
// 使用
NumberBox intBox = new NumberBox<>(); // OK
NumberBox strBox = new NumberBox<>(); // 編譯錯誤:String 不是 Number
Java 泛型採用類型擦除:編譯器將泛型類型替換為原始類型,必要時插入強制轉型。運行時不存在泛型類型信息。
ObjectT extends Number & Comparable → Number)new T[10]、new List[10] 皆編譯錯誤| 限制 | 原因 | 對策 |
|---|---|---|
不能 new T() |
運行時無 T 的 Class 對象 | 工廠模式、傳入 Class、Supplier |
不能 new T[10] |
數組需知確切類型 | 用 List、或 (T[]) new Object[10] (帶警告) |
不能 instanceof T |
運行時無 T 信息 | 檢查原始類型、或傳入 Class |
| 重載方法擦除後衝突 | 擦除後簽名相同 | 方法重命名、或用不同參數類型區分 |
| 靜態字段共享 | 所有實例共享一個原始類 | 不用靜態字段存泛型狀態 |
// 1. 無界通配符:表示「未知類型」,只能讀取 Object、不能寫入 (除 null)
List> unknownList = new ArrayList();
Object obj = unknownList.get(0); // OK
// unknownList.add("test"); // 編譯錯誤
// 2. 上界通配符:? extends T —— 生產者 (Producer) —— 只讀不寫
List extends Number> nums = new ArrayList();
Number n = nums.get(0); // OK,安全讀取為 Number
// nums.add(1); // 編譯錯誤:不能寫入,因為可能是 List
// 3. 下界通配符:? super T —— 消費者 (Consumer) —— 只寫不讀 (讀出是 Object)
List super Integer> ints = new ArrayList();
ints.add(1); // OK,Integer 是 Number 子類
// Number n = ints.get(0); // 編譯錯誤:只能讀為 Object
Object o = ints.get(0); // OK
// 經典案例:Collections.copy
public static void copy(List super T> dest, List extends T> src) {
for (int i = 0; i < src.size(); i++) {
dest.set(i, src.get(i)); // src 只讀 (extends),dest 只寫 (super)
}
}
// 方法簽名設計指南:
// - 如果參數只用於「讀取/生產」元素 → 用 ? extends T
// - 如果參數只用於「寫入/消費」元素 → 用 ? super T
// - 如果既要讀又要寫 → 用精確類型 T (不帶通配符)
T 或 K,V@SuppressWarnings("unchecked")、推斷類型參數ArrayList implements List )// 良好的 API
public interface Repository {
T findById(ID id);
List findAll();
T save(T entity);
void deleteById(ID id);
}
// 避免:原始類型
public interface RawRepository {
Object findById(Object id); // 類型不安全
}
public class Pair {
private final K key;
private final V value;
private Pair(K key, V value) { this.key = key; this.value = value; }
// 類型推導,調用無需顯式指定類型
public static Pair of(K key, V value) {
return new Pair<>(key, value);
}
}
// 使用
Pair pair = Pair.of("age", 25); // 自動推導
// 壞習慣
List list = new ArrayList(); // 原始類型,失去類型檢查
list.add("hello");
list.add(123); // 編譯通過,運行時崩潰
// 好習慣
List
// Guice/Jackson/Gson 核心技巧
public abstract class TypeReference {
private final Type type;
protected TypeReference() {
Type superclass = getClass().getGenericSuperclass();
this.type = ((ParameterizedType) superclass).getActualTypeArguments()[0];
}
public Type getType() { return type; }
}
// 使用:匿名內部類保留泛型信息
TypeReference> ref = new TypeReference>() {};
// ref.getType() 可獲取 List 的完整類型信息
| 錯誤信息 | 原因 | 解決 |
|---|---|---|
| Generic array creation | new T[] 或 new List |
用 List/List |
| Type parameter T cannot be instantiated | new T() |
工廠/反射/傳入 Class |
| Incompatible types: List | 泛型不協變 | 用 List extends Object>、或複製新列表 |
| Static field/method cannot reference type parameter | 靜態上下文用 T | 移至實例字段、或改為泛型方法 |
| Name clash: same erasure | 擦除後方法簽名衝突 | 重命名方法、改參數類型 |
Java 泛型通過編譯期類型參數化 + 運行時類型擦除實現類型安全與向後兼容的平衡。核心掌握點:
善用泛型,讓編譯器成為你最強的測試工具,在編碼階段就消滅 90% 的 ClassCastException。