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

Java 泛型完全指南:泛型類、方法、接口、類型擦除、通配符與邊界

     編輯:Java 相關知識

泛型是 Java 類型系統的參數化能力,將「類型」作為參數傳遞,實現編譯期類型安全與代碼復用的雙贏。本文從泛型類/方法/接口定義、類型擦除原理、通配符與邊界、實戰最佳實踐五個維度,構建完整泛型知識體系。

一、為什麼需要泛型?

// 無泛型:Object + 強轉 + 運行期風險
class Box { Object item; void set(Object o) { item = o; } Object get() { return item; } }
Box b = new Box(); b.set("hello"); String s = (String) b.get(); // 運行期才發現類型錯誤

// 有泛型:類型參數化,編譯期保證安全
class Box { T item; void set(T o) { item = o; } T get() { return item; } }
Box box = new Box<>(); box.set("hello"); String s = box.get(); // 無需強轉,編譯期報錯若傳入 Integer

二、泛型三大定義形式

1. 泛型類

class Pair { // 多類型參數
    private K key; private V value;
    public Pair(K k, V v) { key = k; value = v; }
    public K getKey() { return key; }
    public V getValue() { return value; }
}
// 實例化時具體化類型參數
Pair p = new Pair<>(1, "one");
int k = p.getKey(); // 直接獲取 Integer,無強轉

2. 泛型方法

// 類型參數聲明在修飾符後、返回類型前
public static  void printArray(T[] arr) {
    for (T e : arr) System.out.print(e + " ");
    System.out.println();
}
// 調用時編譯器自動推導類型參數(鑽石推導)
printArray(new Integer[]{1,2,3});
printArray(new String[]{"a","b"});

關鍵區別:泛型方法的類型參數獨立於類,即使在非泛型類中也可定義。

3. 泛型接口

interface Processor { void process(T t); }
class StringProcessor implements Processor { // 實現時固定類型
    public void process(String s) { System.out.println("處理: " + s); }
}
class GenericProcessor implements Processor { // 繼續保持泛型
    public void process(T t) { System.out.println("通用: " + t); }
}

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

Java 泛型採用擦除實現:編譯器檢查類型後,字節碼中不保留具體類型參數,全部替換為原始類型或Object。

// 源碼
List list = new ArrayList<>();
list.add("hello");
// 字節碼等價於
List list = new ArrayList();
list.add("hello"); // 編譯器插入隱式檢查

擦除後果

  • 運行期無法 new T()、T[] arr = new T[10]、obj instanceof T、T.class。
  • 泛型數組創建非法:new List[10] → 編譯錯誤(類型安全漏洞)。
  • 重載失效:void m(List) 和 void m(List) 擦除後同簽名。

補償手段:類型 Token

class TypeRef {} // 匿名子類保留類型信息
TypeRef> ref = new TypeRef>() {};
// 通過反射獲取實際類型參數
ParameterizedType pt = (ParameterizedType) ref.getClass().getGenericSuperclass();
Type[] args = pt.getActualTypeArguments(); // [String]

四、通配符與邊界:靈活控制類型範圍

1. 上界通配符 —— 生產者

// 只能讀取 T 或其子類,不能寫入(除 null)
void printAll(List nums) {
    for (Number n : nums) System.out.println(n.doubleValue());
    // nums.add(1); // 編譯錯誤:編譯器不知具體類型,無法保證類型安全
}
printAll(Arrays.asList(1, 2.5, 3L)); // List, List 均可傳入

2. 下界通配符 —— 消費者

// 只能寫入 T 或其父類,讀取只能作為 Object
void addIntegers(List list) {
    list.add(10);      // OK
    list.add(20);      // OK
    Object o = list.get(0); // 只能作為 Object 讀取
}
List nums = new ArrayList<>();
addIntegers(nums); // List、List 均可

3. PECS 原則

Producer Extends, Consumer Super
只讀 → ? extends T;只寫 → ? super T;又讀又寫 → 不用通配符(用具體類型)。

4. 類型參數邊界

// 限制 T 必須是 Number 子類且實現 Comparable
public > T max(T a, T b) {
    return a.compareTo(b) > 0 ? a : b;
}
// 可調用:max(1, 2), max(1.5, 2.5)
// 不可調用:max("a", "b") // String 非 Number 子類

五、實戰最佳實踐清單

  1. 集合聲明優先用接口:List list = new ArrayList<>();
  2. 方法參數用通配符:void print(Collection c) 最大化靈活性。
  3. 避免原始類型:List 失去類型檢查,僅為兼容舊代碼。
  4. 消除非受檢警告:@SuppressWarnings("unchecked") 僅在證明安全處加注解。
  5. 優先泛型方法而非泛型類:若類型參數僅用於單一方法,定義為泛型方法。
  6. 不要混用泛型與可變參數:void m(T... args) 可能導致堆污染,JDK 7+ 加 @SafeVarargs 抑制警告。
  7. 文檔化類型參數:Javadoc @param 說明。

六、常見編譯錯誤速查

代碼錯誤原因修正
new T()擦除後無構造器信息傳入 Supplier 或工廠
T[] arr = new T[10]泛型數組創建非法用 List 或 (T[]) new Object[10](慎用)
obj instanceof List運行期無類型參數obj instanceof List + 遍歷檢查元素
void m(List); void m(List)擦除後同簽名合併為 void m(List) 或重命名
class A { static T field; }靜態字段不可引用類型參數改為實例字段或泛型方法

泛型看似複雜,核心只有三點:編譯期類型檢查、擦除後運行期無痕、通配符控制協變/逆變。熟練掌握 PECS 原則與邊界聲明,能讓你的 API 既安全又極具彈性。

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