首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 程式設計入門

Java 泛型核心原理與類型擦除實戰指南

     編輯:Java 程式設計入門

泛型本質:參數化類型

泛型讓「類型」成為參數,在類、介面、方法定義時推遲具體類型確定至使用時。核心優勢:

  • 編譯期型別安全:避免運行時 `ClassCastException`。
  • 消除強制轉型:編譯器自動插入隱式轉型,程式碼更乾淨。
  • 程式碼重用:一份邏輯支援多種類型。

五大核心規則與限制

規則說明範例
型別參數只能是引用類型 基本型別需用包裝類 `List` ❌ → `List` ✅
泛型類不同參數化類型不兼容 `List` 不是 `List` 子型別 `List list = new ArrayList();` ❌
支援多型別參數 以逗號分隔 `Map`、`Triple`
有界類型參數 用 `extends` 限定上界 `>`
通配符型別 `?`、`? extends T`、`? super T` `List` 表示 Number 或其子類列表

類型擦除:泛型的「隱形面紗」

Java 泛型為偽泛型,編譯器編譯後會擦除所有型別參數資訊:

  • `List` → `List`(原始類型)
  • `T` → `Object`(無界)或第一個界限類型(有界)
  • 編譯器在呼叫處自動插入 `checkcast` 指令

擦除導致的經典限制

// 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) {} // ❌

等價實現對比:泛型 vs 非泛型

泛型版本

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();

非泛型版本(Object 替代)

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 替代
型別檢查時機編譯期運行期
強制轉型編譯器自動插入(隱式)開發者手寫(顯式)
程式碼可讀性高(意圖明確)低(充滿轉型)
效能略高(避免裝箱/拆箱優化)略低
運行時型別資訊擦除後丟失本就無型別參數

通配符與 PECS 原則實戰

上界通配符 `? extends T` —— 生產者

// 只能讀,不能寫(除 null)
public void printNumbers(List list) {
    for (Number n : list) System.out.println(n);
    // list.add(123); // ❌ 編譯錯誤
}

下界通配符 `? super T` —— 消費者

// 只能寫,讀出只能是 Object
public void addIntegers(List list) {
    list.add(10);      // ✅ Integer 是 Integer 子類
    list.add(new Integer(20)); // ✅
    // Number n = list.get(0); // ❌ 編譯錯誤,只能 Object n = list.get(0)
}

PECS 口訣

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` 並確認內部安全。

總結

  1. 泛型提供編譯期型別安全,核心機制是類型擦除 + 隱式轉型。
  2. 掌握 PECS 原則 正確使用通配符,API 設計更具彈性與安全性。
  3. 警惕擦除帶來的限制:無法實例化型別參數、靜態成員、異常、instanceof 檢查。
  4. 善用泛型方法型別推論 減少冗長宣告。
  5. 處理堆污染 風險:`@SafeVarargs`、避免泛型陣列建立、Varargs 不外洩。

泛型雖非完美(受限於向後相容),但善用其規則能讓 Java 程式碼更健壯、可讀、可維護。Java 10+ 局部變數型別推論 `var` 配合泛型,更進一步簡化樣板代碼。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved