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

Core Java 筆記:泛型核心機制與類型擦除深度解析(上)

     編輯:Java 綜合教學

為什麼需要泛型?

泛型程式設計消除了到處可見的 Object 與強制轉型,提升代碼類型安全性與可讀性,將運行時錯誤前移至編譯期。


泛型類定義

class Pair<T> {
    private T first;
    private T second;
    
    public Pair() {}
    public Pair(T first, T second) { this.first = first; this.second = second; }
    
    public T getFirst() { return first; }
    public T getSecond() { return second; }
    public void setFirst(T v) { first = v; }
    public void setSecond(T v) { second = v; }
}

// 使用
Pair<String> p = new Pair<>("min", "max");
  • <T> 為類型參數
  • Pair<String> 為參數化類型
  • 原始類型:Pair(不帶參數,不推薦使用)

泛型方法定義

class ArrayAlg {
    // 1. 最簡單泛型方法(無限定)
    public static <T> T getMiddle(T[] a) {
        return a[a.length / 2];
    }
    
    // 2. 類型限定:T 必須實作 Comparable
    public static <T extends Comparable> T min(T[] a) {
        if (a == null || a.length == 0) return null;
        T min = a[0];
        for (T e : a) if (min.compareTo(e) > 0) min = e;
        return min;
    }
    
    // 3. 多重限定:<T extends Comparable & Serializable>
    // 4. Comparable 本身是泛型,完整寫法下講介紹
}

類型推斷

String[] names = {"John", "Q.", "Public"};
String mid = ArrayAlg.<String>getMiddle(names);  // 顯式指定
String mid2 = ArrayAlg.getMiddle(names);         // 編譯器自動推斷

基本型別限制

基本型別無法作為類型參數:ArrayAlg.getMiddle(new double[]{1.2}) 編譯錯誤。必須使用包裝類型 Double。


核心原理:類型擦除

JVM 沒有泛型類型物件——所有物件都是普通類物件。泛型純粹是編譯器行為。 這是 Java 與 C++ 模板的本質區別,也是 Java 泛型表達能力受限的根源。

擦除規則

擦除後的 Pair 類

class Pair {
    private Object first;
    private Object second;
    public Pair() {}
    public Pair(Object first, Object second) { ... }
    public Object getFirst() { return first; }
    public Object getSecond() { return second; }
    public void setFirst(Object v) { first = v; }
    public void setSecond(Object v) { second = v; }
}

限定切換與效能

多重限定 <T extends A & B> 會產生限定切換:擦除為第一個類型(A),代碼中必要時插入強轉切換到 B。建議:常用介面放前面,標記介面放後面。


使用端:泛型代碼塊翻譯

Pair<Employee> buddies = ...;
Employee buddy = buddies.getFirst();

// 編譯器自動翻譯為:
Object obj = buddies.getFirst();
Employee buddy = (Employee) obj;  // 插入強制轉型

泛型與多態的衝突與橋接方法

問題 1:擦除導致方法簽名衝突

public class DateInterval extends Pair<Date> {
    @Override
    public void setSecond(Date second) {  // 子類重寫
        if (second.compareTo(getFirst()) >= 0) super.setSecond(second);
    }
}

擦除後:DateInterval 繼承 Pair(擦除後 setSecond(Object)),同時宣告 setSecond(Date)。兩個方法共存,多態似乎失效?

解決:橋接方法

編譯器自動生成橋接方法保護多態語義:

// 反編譯看到:
public void setSecond(Object second) {  // 橋接方法
    setSecond((Date) second);  // 轉發到真正重寫方法
}

public void setSecond(Date second) { ... }  // 真正的重寫方法
  • @Override 實際作用於 setSecond(Object)
  • 橋接方法確保父類引用調用 setSecond(Object) 時正確分派到子類邏輯

問題 2:協變返回類型衝突

public class DateInterval extends Pair<Date> {
    @Override
    public Date getSecond() {  // 協變返回類型
        return (Date) super.getSecond().clone();
    }
}

反編譯發現兩個同名同參數方法共存:

public Date getSecond();      // 協變方法
public Object getSecond();    // 橋接方法

JVM 允許同簽名不同返回類型的方法共存(位元碼層面以返回類型區分),調用時正確分派。


小結

  • 泛型 = 編譯期類型檢查 + 擦除 + 自動插入強轉
  • 無運行時泛型資訊(除簽名保留的簽名註解)
  • 橋接方法解決擦除與多態/協變的衝突
  • 基本型別無法泛型化,需裝箱,有效能成本

下篇將介紹:通配符(? extends T/? super T)、PECS 原則、泛型與陣列的衝突、類型令牌模式等進階主題。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
情況擦除目標範例
無限定 <T>ObjectPair<T> → Pair (欄位變 Object)
有限定 <T extends Comparable>第一個限定類型擦除為 Comparable