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

Effective Java 泛型核心精華:從類型擦除到通配符的完整實戰指南

     編輯:Java 程式設計入門

一、核心原則:拒絕原始類型,擁抱泛型

原始類型(如 List)會繞過泛型檢查,導致運行時 ClassCastException;參數化類型(如 List<String>)在編譯期捕獲類型錯誤,消除強制轉換。

1.1 鑽石語法簡化聲明

// JDK 7+ 鑽石操作符 <> 讓編譯器推斷類型參數
List<String> list = new ArrayList<>(); // 推薦寫法

1.2 List vs List<Object> 關鍵區別

  • List:逃避泛型檢查,可插入任意對象,類型不安全
  • List<Object>:明確聲明持有任意類型,受泛型約束,類型安全

二、類型擦除:泛型的運行時真相

泛型信息在編譯後被擦除,List<String> 和 List<Integer> 運行時都是 List。

2.1 擦除演示

List<String> strList = new ArrayList<>();
List<Integer> intList = new ArrayList<>();
System.out.println(strList.getClass() == intList.getClass()); // true

// 通過反射繞過編譯檢查(僅作演示,生產禁用)
Method m = intList.getClass().getMethod("add", Object.class);
m.invoke(intList, "string"); // 運行時成功插入字符串!
System.out.println(intList); // [string]

2.2 必須使用原始類型的兩種場景

  1. Class 字面量:List.class 合法,List<String>.class 非法
  2. instanceof 檢查:if (o instanceof Set) 再轉型 Set<?>

三、消除非受檢警告:精準控制 @SuppressWarnings

原則:範圍最小化,優先修復代碼而非抑制警告。

// 差:整個方法抑制
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) { ... }

// 優:僅在必要變量聲明抑制
public <T> T[] toArray(T[] a) {
    if (a.length < size) {
        @SuppressWarnings("unchecked") 
        T[] result = (T[]) Arrays.copyOf(elementData, size, a.getClass());
        return result;
    }
    ...
}

四、列表優於數組:協變與擦除的權衡

特性數組泛型
類型關係協變:String[] 是 Object[] 子類型不可變:List<String> 不是 List<Object> 子類型
類型檢查運行時檢查(具體化)編譯時檢查,運行時擦除
錯誤發現運行時 ArrayStoreException編譯時錯誤
Object[] objArr = new String[1];
objArr[0] = 3; // 編譯通過,運行拋異常

List<Object> objList = new ArrayList<String>(); // 編譯錯誤
objList.add(3); // 編譯直接攔截

五、自定義泛型類與方法

5.1 泛型類:類型安全的棧實現

public class Stack<E> {
    private E[] elements;
    private int size = 0;
    private static final int DEFAULT_CAPACITY = 16;

    @SuppressWarnings("unchecked")
    public Stack() {
        elements = (E[]) new Object[DEFAULT_CAPACITY]; // 必須抑制警告
    }

    public void push(E e) {
        ensureCapacity();
        elements[size++] = e;
    }

    public E pop() {
        if (size == 0) throw new EmptyStackException();
        E e = elements[--size];
        elements[size] = null; // 消除過期引用
        return e;
    }
    // ...
}

5.2 泛型方法:靜態工具方法首選

// 聯合兩個集合
public static <E> Set<E> union(Set<E> s1, Set<E> s2) {
    Set<E> result = new HashSet<>(s1);
    result.addAll(s2);
    return result;
}

// 類型安全的異構容器模式
public static <K, V> Map<K, V> newHashMap() {
    return new HashMap<K, V>();
}

5.3 泛型單例工廠模式

private static UnaryFunction<Object> IDENTITY_FN = 
    new UnaryFunction<Object>() {
        public Object apply(Object arg) { return arg; }
    };

@SuppressWarnings("unchecked")
public static <T> UnaryFunction<T> identityFunction() {
    return (UnaryFunction<T>) IDENTITY_FN; // 利用擦除共享單例
}

5.4 遞歸類型限制

<T extends Comparable<T>> // T 能與自身比較
<T extends Comparable<? super T>> // 更靈活:T 能與父類型比較

六、通配符類型:API 靈活性的關鍵

6.1 無限制通配符 List<?>

表示未知類型的列表,只能讀取 Object,不能添加元素(除 null)。

6.2 有限制通配符:PECS 原則

Producer Extends, Consumer Super

  • <? extends T>:生產者(讀取),只能獲取 T 或子類,不能寫入
  • <? super T>:消費者(寫入),可接受 T 或父類,讀取只能作 Object

6.3 實戰:靈活的棧 API

public class Stack<E> {
    // pushAll:元素生產者 → extends
    public void pushAll(Iterable<? extends E> src) {
        for (E e : src) push(e);
    }

    // popAll:元素消費者 → super
    public void popAll(Collection<? super E> dst) {
        while (!isEmpty()) dst.add(pop());
    }
}

// 使用示例
Stack<Number> numStack = new Stack<>();
Iterable<Integer> ints = Arrays.asList(1,2,3);
Collection<Object> objs = new ArrayList<>();

numStack.pushAll(ints);   // Integer extends Number ✓
numStack.popAll(objs);    // Object super Number ✓

七、總結:泛型最佳實踐清單

  1. 新代碼禁用原始類型,善用鑽石語法
  2. 編寫泛型類/方法替代強制轉換,提升類型安全
  3. API 參數應用 extends/super 通配符增強靈活性
  4. @SuppressWarnings("unchecked") 僅用於無法消除的合理轉換,並縮小範圍
  5. 優先用 List<E> 替代 E[],避免數組與泛型混用
  6. 理解類型擦除機制,不嘗試運行時檢查泛型類型參數
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved