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

Java 泛型深度解析:從類型擦除到通配符實戰

     編輯:Java 相關知識

泛型是 JDK 1.5 引入的核心特性,實現了「參數化型別」,將型別檢查前置到編譯期,大幅提升程式碼安全性與復用性。本文從泛型類/介面/方法定義、通配符上下界、類型擦除機制到實戰避坑,帶你徹底掌握 Java 泛型精髓。

為什麼需要泛型?

JDK 1.5 前 Comparable 定義為 compareTo(Object o),執行期才發現型別不匹配拋 ClassCastException。泛型版 Comparable<T> 讓編譯器直接攔截錯誤:

// 編譯期報錯:String 不能傳給 compareTo(Date)
Comparable<Date> cmp = new Date();
cmp.compareTo("2024-01-01"); // 錯誤:The method compareTo(Date) ...

泛型三大定義形式

1. 泛型類

public class Box<T> {
    private T value;
    public void set(T v) { value = v; }
    public T get() { return value; }
}

// 使用:具體化型別參數
Box<String> box = new Box<>(); // 鑽石運算符 JDK 7+
box.set("Hello");
String s = box.get(); // 無需強轉

2. 泛型介面

public interface Processor<T> {
    void process(T t);
}

// 實現類指定具體型別
class StringProcessor implements Processor<String> { ... }
// 或保持泛型
class GenericProcessor<T> implements Processor<T> { ... }

3. 泛型方法

public static <T> void printArray(T[] arr) {
    for (T t : arr) System.out.print(t + " ");
    System.out.println();
}

// 呼叫時型別推導
printArray(new Integer[]{1,2,3});
printArray(new String[]{"A","B"});
// 顯式指定型別(型別推導失敗時)
MyClass.<Double>printArray(new Double[]{1.1, 2.2});

通配符:解決泛型協變/逆變難題

泛型預設為不變:List<Integer> 不是 List<Number> 的子型別。通配符打破此限制:

上界通配符 <? extends T> —— 只讀場景

// 可接受 List、List 等 Number 子類
public static double sum(List<? extends Number> list) {
    double s = 0;
    for (Number n : list) s += n.doubleValue(); // 只能讀,取出即為 Number
    return s;
}
// list.add(1); // 編譯錯誤:無法寫入具體子類

下界通配符 <? super T> —— 只寫場景

// 可接受 List、List 等 Integer 父類
public static void addIntegers(List<? super Integer> list) {
    list.add(1);      // 寫入安全:Integer 是 ? super Integer 的子類
    list.add(2);
    // Number n = list.get(0); // 編譯錯誤:取出只能是 Object
}


無界通配符 <?> —— 僅操作結構

public static void printList(List<?> list) {
    for (Object o : list) System.out.println(o); // 僅能當 Object 讀
}

PECS 原則速記

Producer Extends, Consumer Super
作為生產者(只讀取元素)用 extends;作為消費者(只寫入元素)用 super。

類型擦除:泛型的運行期真相

  • 編譯器用泛型資訊做型別檢查與隱式強轉,位元組碼中不保留具體型別參數。
  • List<String> 和 List<Integer> 編譯後皆為原始 List。
  • 運行期 list instanceof List<String> 編譯錯誤,只能 list instanceof List。
List<String> ls = new ArrayList<>();
List<Integer> li = new ArrayList<>();
System.out.println(ls.getClass() == li.getClass()); // true

泛型最佳實踐與避坑指南

  • ✅ 盡量使用泛型,避免原始型別。
  • ✅ 方法簽名優先用通配符(List<? extends E>),提升 API 靈活度。
  • ❌ 不要建立泛型陣列:new T[10] 編譯錯誤,改用 List<T> 或 (T[]) new Object[10](有非受檢警告)。
  • ❌ 靜態欄位/方法不可使用類的型別參數。
  • ❌ 不能用 instanceof 檢查具體泛型型別。
  • ⚠️ 異常類別不能是泛型(extends Throwable 限制)。
  • ✅ 文件註解用 @param <T> 說明型別參數語義。

實戰範例:泛型 DAO 基類

public abstract class BaseDao<T, ID> {
    public abstract T findById(ID id);
    public abstract List<T> findAll();
    public abstract void save(T entity);
    public abstract void delete(ID id);
}

// 具體實現
public class UserDao extends BaseDao<User, Long> { ... }

總結

泛型將型別安全從執行期提前到編譯期,配合通配符上下界實現協變/逆變,是 Java 型別系統的基石。深入理解類型擦除機制,能避免「泛型在運行期消失」帶來的誤區。建議下一步:閱讀 java.util.Collections、java.util.stream.Stream 源碼,觀察標準庫如何大規模運用泛型設計優雅 API。

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