泛型讓「類型」成為參數,在類、介面、方法定義時推遲具體類型確定至使用時。核心優勢:
| 規則 | 說明 | 範例 |
|---|---|---|
| 型別參數只能是引用類型 | 基本型別需用包裝類 | `List |
| 泛型類不同參數化類型不兼容 | `List |
`List |
| 支援多型別參數 | 以逗號分隔 | `Map |
| 有界類型參數 | 用 `extends` 限定上界 | ` |
| 通配符型別 | `?`、`? extends T`、`? super T` | `List extends Number>` 表示 Number 或其子類列表 |
Java 泛型為偽泛型,編譯器編譯後會擦除所有型別參數資訊:
// 1. 無法 new T()、new T[]、T.class
public void test() {
T obj = new T(); // ❌ 編譯錯誤
T[] arr = new T[10]; // ❌
Class clazz = T.class; // ❌
}
// 2. 無法 instanceof 泛型型別
if (list instanceof List) {} // ❌ 僅能 instanceof List>
// 3. 靜態成員無法使用型別參數
class Box {
static T item; // ❌
static void print(T t) {} // ❌
}
// 4. 異常類別不能是泛型
class MyException extends Exception {} // ❌
catch (MyException e) {} // ❌
public class Gen {
private T ob;
public Gen(T ob) { this.ob = ob; }
public T getOb() { return ob; }
public void setOb(T ob) { this.ob = ob; }
public void showType() {
System.out.println("T 的實際類型: " + ob.getClass().getName());
}
}
Gen intOb = new Gen<>(88); // 自動裝箱
int i = intOb.getOb(); // 無需強轉
Gen strOb = new Gen<>("Hi");
String s = strOb.getOb();
public class Gen2 {
private Object ob;
public Gen2(Object ob) { this.ob = ob; }
public Object getOb() { return ob; }
public void setOb(Object ob) { this.ob = ob; }
public void showType() {
System.out.println("類型: " + ob.getClass().getName());
}
}
Gen2 intOb = new Gen2(88); // 自動裝箱
int i = (Integer) intOb.getOb(); // ⚠️ 必須強轉,風險在運行時
Gen2 strOb = new Gen2("Hi");
String s = (String) strOb.getOb();
| 維度 | 泛型 | Object 替代 |
|---|---|---|
| 型別檢查時機 | 編譯期 | 運行期 |
| 強制轉型 | 編譯器自動插入(隱式) | 開發者手寫(顯式) |
| 程式碼可讀性 | 高(意圖明確) | 低(充滿轉型) |
| 效能 | 略高(避免裝箱/拆箱優化) | 略低 |
| 運行時型別資訊 | 擦除後丟失 | 本就無型別參數 |
// 只能讀,不能寫(除 null)
public void printNumbers(List extends Number> list) {
for (Number n : list) System.out.println(n);
// list.add(123); // ❌ 編譯錯誤
}
// 只能寫,讀出只能是 Object
public void addIntegers(List super Integer> list) {
list.add(10); // ✅ Integer 是 Integer 子類
list.add(new Integer(20)); // ✅
// Number n = list.get(0); // ❌ 編譯錯誤,只能 Object n = list.get(0)
}
Producer Extends, Consumer Super
只讀取(生產)用 `extends`,只寫入(消費)用 `super`,既要讀又要寫不用通配符。
// 靜態泛型方法,型別參數在方法簽名中宣告
public static void swap(List list, int i, int j) {
T temp = list.get(i);
list.set(i, list.get(j));
list.set(j, temp);
}
// 呼叫時編譯器自動推論 T 為 String
List list = Arrays.asList("A", "B", "C");
swap(list, 0, 2); // 等同 swap(list, 0, 2)
@SafeVarargs // Java 7+ 抑制 unchecked 警告
public static void addAll(List list, T... elements) {
for (T e : elements) list.add(e);
}
// 危險範例:堆污染導致遠端 ClassCastException
List strList = new ArrayList<>();
addAll(strList, "A", "B"); // 正常
addAll(strList, 123); // 編譯警告但通過!運行時在取出時拋出 CCE
規則:泛型可變參數方法不得將參數陣列暴露給外部(如賦值給欄位、返回、傳給其他方法),否則加 `@SafeVarargs` 並確認內部安全。
泛型雖非完美(受限於向後相容),但善用其規則能讓 Java 程式碼更健壯、可讀、可維護。Java 10+ 局部變數型別推論 `var` 配合泛型,更進一步簡化樣板代碼。