在 Java 早期,容器類(如 `ArrayList`)只能存放 `Object`,取出時需強制轉型,既不安全又繁瑣。泛型的引入,實現了**類型參數化**:同一套代碼可操作多種數據類型,且由編譯器保證類型安全,消除運行時 `ClassCastException`,提升代碼可讀性與復用性。本文系統講解泛型類、方法、介面、類型擦除機制、邊界限定及容器實戰。
// 單類型參數
public class Pair<T> {
private T first;
private T second;
public Pair(T first, T second) { this.first = first; this.second = second; }
public T getFirst() { return first; }
public T getSecond() { return second; }
}
// 使用:傳遞實際類型參數
Pair<Integer> range = new Pair<>(1, 100); // Java 7+ 鑽石語法省略後置類型
Pair<String> kv = new Pair<>("name", "張三");
// 多類型參數
public class Pair<U, V> { U first; V second; /* 省略構造器/Getter */ }
Pair<String, Integer> pair = new Pair<>("age", 25);
**Java 泛型僅存在於編譯期**,JVM 運行時不知泛型。編譯器將泛型類「擦除」為原始類型:
// 擦除後等價代碼
public class Pair {
private Object first, second;
public Pair(Object first, Object second) { ... }
public Object getFirst() { return first; }
// 調用處自動插入轉型:Integer i = (Integer) pair.getFirst();
}
關鍵認知:運行時 `Pair<Integer>` 僅是 `Pair`,無法獲知 `Integer`。這是理解泛型局限(如不能 `new T()`、`instanceof T`、靜態成員無法使用類型參數)的根本。
為了**向後兼容**。Java 5 引入泛型時,不修改 JVM 規範,僅靠編譯器實現,使泛型代碼能在舊版 JVM 運行(無泛型感知),保證了生態平滑遷移。
泛型最典型應用是容器類。我們實現一個簡化版 `ArrayList` —— `DynamicArray<E>`:
public class DynamicArray<E> {
private static final int DEFAULT_CAPACITY = 10;
private int size = 0;
private Object[] elementData; // 底層仍用 Object 數組
public DynamicArray() { elementData = new Object[DEFAULT_CAPACITY]; }
// 擴容邏輯
private void ensureCapacity(int minCapacity) {
if (elementData.length >= minCapacity) return;
int newCapacity = elementData.length * 2;
if (newCapacity < minCapacity) newCapacity = minCapacity;
elementData = Arrays.copyOf(elementData, newCapacity);
}
public void add(E e) {
ensureCapacity(size + 1);
elementData[size++] = e; // 存入時隱式向上轉型為 Object
}
@SuppressWarnings("unchecked")
public E get(int index) {
return (E) elementData[index]; // 取出時編譯器插入向下轉型
}
// 其他方法:size(), set(), remove() 等...
}
使用示例:
DynamicArray<Double> arr = new DynamicArray<>();
arr.add(Math.random());
Double d = arr.get(0); // 無需手動轉型,類型安全
// 巢狀泛型
DynamicArray<Pair<Integer, String>> complexArr = new DynamicArray<>();
方法獨立於類是否泛型,可自行定義類型參數:
// 單類型參數,編譯器自動推斷實際類型
public static <T> int indexOf(T[] arr, T element) {
for (int i = 0; i < arr.length; i++)
if (arr[i].equals(element)) return i;
return -1;
}
// 調用:indexOf(new Integer[]{1,2,3}, 2) → 推斷 T 為 Integer
// indexOf(new String[]{"a","b"}, "b") → 推斷 T 為 String
// 多類型參數
public static <U, V> Pair<U, V> makePair(U first, V second) {
return new Pair<>(first, second);
}
Pair<String, Integer> p = makePair("score", 100); // 自動推斷 U=String, V=Integer
// JDK 核心庫範例
public interface Comparable<T> { int compareTo(T o); }
public interface Comparator<T> { int compare(T o1, T o2); }
// 實現時指定具體類型
public final class Integer extends Number implements Comparable<Integer> {
public int compareTo(Integer another) { ... }
}
// 匿名類實現 Comparator
Comparator<String> cmp = new Comparator<String>() {
public int compare(String s1, String s2) { return s1.compareToIgnoreCase(s2); }
};
預設類型參數僅擁有 `Object` 方法。通過 `
public class NumberPair<U extends Number, V extends Number> extends Pair<U, V> {
public double sum() {
return getFirst().doubleValue() + getSecond().doubleValue(); // 可調用 Number 方法
}
}
NumberPair<Integer, Double> np = new NumberPair<>(10, 3.14);
double s = np.sum(); // 13.14
// 錯誤寫法:警告 "Comparable is a raw type"
public static <T extends Comparable> T max(T[] arr) { ... }
// 正確寫法:遞歸類型限定
public static <T extends Comparable<T>> T max(T[] arr) {
T max = arr[0];
for (int i = 1; i < arr.length; i++)
if (arr[i].compareTo(max) > 0) max = arr[i];
return max;
}
// 含義:T 必須實現 Comparable,即能與「同類型」比較
問題:`DynamicArray<Integer>` **不是** `DynamicArray<Number>` 的子類(泛型不變性)。直接寫 `addAll(DynamicArray<E> c)` 會拒絕傳入 `DynamicArray<Integer>`。
解決:方法自定義類型參數 `
public class DynamicArray<E> {
// E 是類的類型參數,T 是方法的類型參數,T 上界為 E
public <T extends E> void addAll(DynamicArray<T> c) {
for (int i = 0; i < c.size(); i++) add(c.get(i)); // T 是 E 子類,安全添加
}
}
// 現在合法:
DynamicArray<Number> numbers = new DynamicArray<>();
DynamicArray<Integer> ints = new DynamicArray<>();
numbers.addAll(ints); // 編譯通過,運行安全
JDK 8 定義核心函數式介面,支援 Lambda 表達式:
| 介面 | 參數 | 返回 | 抽象方法 | 典型用途 |
|---|---|---|---|---|
| Predicate<T> | T | boolean | test(T) | 條件判斷、過濾 |
| Consumer<T> | T | void | accept(T) | 消費型操作(打印、存儲) |
| Function<T,R> | T | R | apply(T) | 轉換、映射 |
| Supplier<T> | 無 | T | get() | 延遲生成、工廠 |
| UnaryOperator<T> | T | T | apply(T) | 一元運算(同類型轉換) |
| BinaryOperator<T> | T, T | T | apply(T,T) | 二元運算(聚合、合併) |
另有針對基本類型的特化版本(`IntPredicate`, `LongConsumer`, `DoubleFunction` 等),避免裝箱拆箱開銷。
下一章將深入剖析令人「費解」的**泛型通配符**(` extends T>` vs ` super T>`),掌握 PECS 原則,徹底搞懂協變與逆變在 Java 中的實際應用。