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

Java 泛型核心原理與實戰:定義、通配符、類型擦除、Class 泛型化與反射工廠模式

     編輯:Java 程式設計入門

前言

Java 泛型是編譯期類型系統的擴展,通過**參數化類型**實現代碼複用與類型安全。本文系統剖析**泛型類/方法定義、通配符上下界 (PECS)、類型擦除本質與後果、Class<T> 泛型化反射工廠、陣列與泛型的不兼容、最佳實踐模式**,助你從「會用」進化到「懂原理、能設計 API、避坑」。

一、泛型定義語法與規則

1. 泛型類


// 多類型參數、上界限定
public class Pair> {
    private final K key;
    private final V value;
    public Pair(K key, V value) { this.key = key; this.value = value; }
    public K getKey() { return key; }
    public V getValue() { return value; }
}

// 靜態成員不可使用類的類型參數
// private static T single; // ❌ Compile Error
// public static T get() {}  // ❌

2. 泛型方法


public class Util {
    // 類型參數在修飾符後、返回類型前
    public static  T first(List list) {
        return list.isEmpty() ? null : list.get(0);
    }

    // 多參數、上界、返回類型關聯
    public static > T max(T a, T b) {
        return a.compareTo(b) > 0 ? a : b;
    }

    // 通配符參數:只讀用 extends,只寫用 super
    public static void copy(List dest, List src) {
        for (T t : src) dest.add(t);
    }
}

3. 類型參數命名慣例

字母含義典型場景
TType通用類型參數
EElement集合元素類型
K/VKey/ValueMap 鍵值
RReturn返回類型
S/U/V第 2/3/4 類型參數多參數泛型方法

二、通配符與 PECS 原則深度解析

1. 三種通配符形式


List              // 無界通配符:只能讀 Object、只能寫 null
List // 上界通配符:協變,只能讀 Number 及父類、不能寫 (除 null)
List  // 下界通配符:逆變,只能寫 Integer 及子類、讀只能得 Object

2. PECS 實戰:API 設計決策表

場景角色簽名理由
集合作為數據來源Producer`void process(List source)`支援子類列表,安全讀取
集合作為數據目標Consumer`void collect(List target)`支援父類列表,安全寫入
既要讀又要寫-`void transfer(List list)`**不用通配符**,用具體類型參數
比較器Consumer`Comparator`可用父類比較器比較子類對象
工廠/生產者Producer`Supplier`可返回子類實例

3. 類型推導實例


List ints = List.of(1,2,3);
List nums = new ArrayList<>();

// copy(List dest, List src)
// T 推導為 Integer
Util.copy(nums, ints); // ✅ nums 是 List,Number 是 Integer 父類 → ? super Integer
// Util.copy(ints, nums); // ❌ ints 是 List,不是 ? super Number

三、類型擦除:本質、後果與補償

1. 擦除規則

  • **無界擦除至 Object**:List → List (raw type)。
  • **有界擦除至首個界限**:T extends Number & Comparable → Number。
  • **擦除後插入強制轉型**:編譯器在調用處自動插入 checkcast。

2. 經典後果與對策

現象原因對策
`new T()` ❌運行期無類型信息用 `Supplier`、`Class`、反射 `clazz.getDeclaredConstructor().newInstance()`
`T[] arr = new T[10]` ❌陣列需具體組件類型用 `List`、`(T[]) new Object[10]` (不安全)、`Array.newInstance(clazz, 10)`
`instanceof T` ❌運行期 T 已擦除用 `clazz.isInstance(obj)`、`obj.getClass() == clazz`
泛型陣列 `new List[10]` ❌類型安全漏洞 (heap pollution)用 `List>`、或 `@SafeVarargs` 方法參數
重載消失 `void m(List)` / `void m(List)`擦除後同簽名改方法名、或用不同參數位置區分
靜態成員共享擦除後單一 Class 物件靜態域/方法勿用類型參數

3. 橋接方法


// 源碼
class MyList implements List {
    public void add(T t) { ... }
}

// 編譯器生成橋接方法 (字節碼)
public void add(Object o) { // 擦除後簽名
    add((T) o); // 調用真實方法,插入 checkcast
}

四、Class<T> 泛型化與反射工廠模式

1. Class<T> 語義

  • `String.class` 型別為 `Class`
  • `List.class` 型別為 `Class` (raw type)
  • `new ArrayList().getClass()` → `Class` (運行期擦除)

2. 類型安全反射工廠


public interface Factory {
    T create();
}

public class ReflectionFactory implements Factory {
    private final Class clazz;
    public ReflectionFactory(Class clazz) { this.clazz = clazz; }

    @Override
    public T create() {
        try {
            return clazz.getDeclaredConstructor().newInstance();
        } catch (ReflectiveOperationException e) {
            throw new IllegalStateException("Cannot instantiate " + clazz, e);
        }
    }
}

// 使用:類型安全、無需強轉
Factory userFactory = new ReflectionFactory<>(User.class);
User u = userFactory.create(); // 型別為 User,非 Object

3. JDBC 查詢映射實戰


public  List query(String sql, Class rowType, Object... params) {
    List results = new ArrayList<>();
    try (Connection c = ds.getConnection();
         PreparedStatement ps = c.prepareStatement(sql)) {
        for (int i = 0; i < params.length; i++) ps.setObject(i+1, params[i]);
        try (ResultSet rs = ps.executeQuery()) {
            // 利用 Class 獲取構造函數/字段,構建 T 實例
            Constructor ctor = rowType.getDeclaredConstructor();
            while (rs.next()) {
                T obj = ctor.newInstance();
                // 反射/Setter/Mapper 填充字段
                mapRow(rs, obj);
                results.add(obj);
            }
        }
    } catch (SQLException | ReflectiveOperationException e) {
        throw new DataAccessException(e);
    }
    return results;
}

// 調用
List users = dao.query("SELECT * FROM users WHERE age > ?", User.class, 18);

五、陣列與泛型:核心不兼容性


// ❌ 編譯錯誤:泛型陣列創建非法
List[] arr = new List[10];

// ✅ 合法:原始類型陣列 + 無檢查轉型 (開發者自擔風險)
@SuppressWarnings("unchecked")
List[] arr = (List[]) new List[10]; // 或 new List[10]

// 原因:陣列協變 + 泛型不變 = 類型安全漏洞
Object[] objArr = new String[10];
objArr[0] = 123; // 運行期 ArrayStoreException

// 若允許泛型陣列
List[] strLists = new List[10];
Object[] objLists = strLists; // 陣列協變,合法
objLists[0] = new ArrayList(); // 運行期無異常!
strLists[0].add("hello"); // 向 List 加 String → 後續讀取 ClassCastException

六、最佳實踐清單

  1. API 邊界優先用泛型:集合、工具類、DAO、Service 接口。
  2. 通配符遵循 PECS:只讀 `extends`、只寫 `super`、雙向不用通配符。
  3. 避免泛型陣列:用 `List>`、`Map>`。
  4. 反射/工廠用 `Class` 傳遞類型信息:保留運行期類型。
  5. 文檔化類型參數約束:`@param must implement Comparable`。
  6. 啟用編譯器警告:`-Xlint:unchecked`、`@SuppressWarnings("unchecked")` 精確標註。
  7. 不要為了泛型而泛型:單一具體類型場景直接用具體類別。

💡 核心心法:泛型是編譯期遊戲,擦除後只剩 Object。設計 API 時:**生產者用 `extends`,消費者用 `super`,需要運行期類型傳 `Class`,陣列別用泛型**。

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