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

Java 泛型完整指南:從基礎語法到類型擦除與反射實戰

     編輯:Java 相關知識

核心術語

術語範例說明
泛型類型ArrayList<E>帶類型參數的類/介面宣告
原始類型ArrayList擦除類型參數後的類型
類型參數E佔位符
參數化類型ArrayList<Integer>具體化類型參數
實際類型參數Integer傳入的具體類型

基本規則與陷阱

1. 參數化類型與原始類型互相兼容(但有警告)

ArrayList raw = new ArrayList<Integer>(); // 無警告
ArrayList<Integer> generic = new ArrayList(); // 未檢查警告

2. 參數化類型不考慮類型參數繼承關係(不變性)

ArrayList<String> s = new ArrayList<Object>(); // 編譯錯誤
ArrayList<Object> o = new ArrayList<String>(); // 編譯錯誤
// 原因:若允許,可向 ArrayList<String> 放入 Integer 破壞類型安全
ArrayList raw = new ArrayList<Integer>();
ArrayList<String> s2 = raw; // 編譯通過(原始類型繞過檢查),運行時風險

3. 通配符 ? 及其邊界

// 上界:? extends Number → 只能讀,不能寫(除 null)
List<? extends Number> nums = new ArrayList<Integer>();
Number n = nums.get(0); // 安全
// nums.add(1); // 編譯錯誤

// 下界:? super Integer → 可寫 Integer 及其子類,讀出只能是 Object
List<? super Integer> ints = new ArrayList<Number>();
ints.add(1); // 安全
Object o = ints.get(0); // 僅能作為 Object 讀取

口訣:PECS —— Producer Extends, Consumer Super。

類型擦除

  • 泛型僅存在於編譯期,編譯器檢查後擦除類型資訊,生成原始類型位元碼。
  • 運行時 new ArrayList<Integer>().getClass() == new ArrayList<String>().getClass() 為 true。
  • 可用反射繞過編譯器檢查,向泛型集合塞入異類元素(強烈不建議)。

自定義泛型方法

public static <T> void swap(T[] a, int i, int j) {
    T tmp = a[i]; a[i] = a[j]; a[j] = tmp;
}

// 呼叫
swap(new String[]{"a","b"}, 0, 1); // T 推斷為 String
swap(new Integer[]{1,2}, 0, 1);    // T 推斷為 Integer
// swap(new int[]{1,2}, 0, 1);     // 錯誤:基本類型非引用類型(但自動裝箱後 Integer 可行)

類型推斷規則

  1. 單一出現點:直接按該處類型推斷。
  2. 多出現點且類型一致:按該類型。
  3. 多出現點類型不一致且無返回值:取最小公共父類(如 Integer 和 Double → Number)。
  4. 有返回值:優先按返回值類型推斷,衝突則編譯錯誤。
  5. 傳遞性:copy(List<String>, String[]) 會按 List<String> 推斷 T 為 String,導致第二參數類型不匹配報錯。

自定義泛型類

public class GenericDao<T> {
    public void add(T entity) { /* ... */ }
    public T findById(int id) { return null; }
    public void delete(T entity) { /* ... */ }
    // 靜態成員不能使用類的類型參數 T
    // public static T staticMethod() { } // 編譯錯誤
}

泛型方法 vs 泛型類的類型參數

class Box<T> {
    T field; // 類的 T
    public <U> U method(U u) { return u; } // 方法的 U,與類的 T 無關
}

泛型與反射:獲取實際類型參數

public class GenericReflection {
    public void apply(Map<Integer, String> map) {}

    public static void main(String[] args) throws Exception {
        Method m = GenericReflection.class.getMethod("apply", Map.class);
        Type[] types = m.getGenericParameterTypes();
        // types[0] -> Map
        ParameterizedType pt = (ParameterizedType) types[0];
        System.out.println("原始類型: " + pt.getRawType()); // interface java.util.Map
        System.out.println("實際參數0: " + pt.getActualTypeArguments()[0]); // class java.lang.Integer
        System.out.println("實際參數1: " + pt.getActualTypeArguments()[1]); // class java.lang.String
    }
}

關鍵 API:Method.getGenericParameterTypes()、Field.getGenericType()、ParameterizedType.getActualTypeArguments()。

總結

  1. 泛型提供編譯期類型安全,運行時擦除。
  2. 善用 ? extends/? super 實現協變/逆變,遵循 PECS 原則。
  3. 避免原始類型與參數化類型混用,消除未檢查警告。
  4. 反射可獲取擦除後丟失的類型資訊,支援框架級開發。
  5. 基本類型不可作為類型參數,依賴自動裝箱(int → Integer)。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved