CodeGym /課程 /JAVA 25 SELF /Generics wildcards

Generics wildcards

JAVA 25 SELF
等級 27 , 課堂 4
開放

1. Generics 的不變性:List<Number> ≠ List<Integer>

在 Java 中,泛型的設計是嚴格的:它們是不變的。這代表即使一個型別是另一個型別的子型別(例如,IntegerNumber 的子型別),帶有這些型別參數的集合也彼此無關。

想像一下:你有一個裝蘋果的盒子(List<Integer>),有人說這其實是「裝水果的盒子」(List<Number>)。看起來好像有道理,畢竟蘋果是水果。但這樣一來就能把香蕉(Double)放進去,一切就壞掉了——因為這盒其實是「蘋果盒」。

程式碼範例:

List<Integer> intList = new ArrayList<>();
List<Number> numList = intList; // 編譯錯誤!

編譯器在此處特別嚴格:它不允許把「蘋果清單」變成「水果清單」。否則我們就能往裡面加任何東西,程式會在執行期間才崩潰。

因此,List<Number>List<Integer> 是兩個完全不同的型別,儘管 Integer 本身是 Number 的子型別。

與陣列相反:

Integer[] intArr = new Integer[10];
Number[] numArr = intArr; // 允許(陣列具共變性)
numArr[0] = 3.14; // 執行階段錯誤(ArrayStoreException)

結論:為了在編譯期確保型別安全,泛型是不變的。

2. 型別參數的界限

有時需要限制泛型類別或方法可使用的型別。為此會使用界限(bounds)

上界(extends

class Stats<T extends Number> {
    private T[] nums;
    // ...
}

現在 Stats 只能用於是 Number 子型別的型別(IntegerDoubleFloat 等)。嘗試建立 Stats<String> 會導致編譯錯誤。

以介面作為限制

class Sorter<T extends Comparable<T>> {
    void sort(List<T> list) { /* ... */ }
}

現在 Sorter 只能用於實作 Comparable 介面的型別。

多重界限

可以透過 & 一次指定多個限制:

class MyClass<T extends Number & Comparable<T>> { /* ... */ }

T 必須是 Number 的子型別,要實作 Comparable<T>

順序很重要:先類別,再介面。

3. 認識 wildcard:?

Wildcard 是一種「佔位」型別,表示:「這裡可以是某個型別,但我不知道確切是哪一個」。

範例:

  • List<?> —— 任意型別的清單。
  • List<? extends Number> —— 任何 Number 子型別的清單(例如 IntegerDouble)。
  • List<? super Integer> —— 任何 Integer 超型別的清單(例如 IntegerNumberObject)。

為什麼需要 wildcards?

它們能讓方法處理彼此相關但不同的型別集合。

範例:唯讀(producer)

void printNumbers(List<? extends Number> list) {
    for (Number n : list) {
        System.out.println(n);
    }
    // list.add(123); // 錯誤!不可新增元素
}
  • 可以把元素當作 Number 讀取。
  • 不可新增元素(除了 null)。

範例:只寫(consumer)

void addIntegers(List<? super Integer> list) {
    list.add(42); // OK
    // Integer x = list.get(0); // 錯誤!無法得知會回傳哪種型別
}
  • 可以加入 Integer(或其子型別)的元素。
  • 無法安全地把元素以 Integer 讀出(只能當作 Object)。

PECS 規則

PECS —— 「Producer Extends, Consumer Super」:

  • Producer — Extends:若集合只「提供」資料(producer),使用 ? extends T
  • Consumer — Super:若集合只「接收」資料(consumer),使用 ? super T

好記口訣:extends——用於讀取(producer),super——用於寫入(consumer)。

比較:陣列具共變性,泛型具不變性

  • 陣列共變:可以把 Integer[] 指派給 Number[] 變數。
  • 泛型不變:不可把 List<Integer> 指派給 List<Number> 變數。

Wildcards 可在一定程度上「緩解」泛型的不變性。

5. 泛型方法與型別推論;限制(type erasure)

泛型方法

可以宣告能處理任意型別的泛型方法:

public static <T> void printList(List<T> list) {
    for (T elem : list) {
        System.out.println(elem);
    }
}

型別推論(type inference)

Java 可以「推斷」型別參數:

List<String> strings = List.of("a", "b");
printList(strings); // T = String

泛型的限制:型別擦除

  • 在 Java 中,泛型透過型別擦除實作:編譯後會移除型別參數資訊。
  • 在位元組碼中,List<String>List<Integer> 沒有差別。
  • 不能建立泛型型別的陣列:new List<String>[10] —— 錯誤。
  • 不能對型別參數使用 instanceofobj instanceof List<String> —— 錯誤。

6. 集合與 Stream API 的實作練習

在清單之間複製元素

public static <T> void copy(List<? super T> dest, List<? extends T> src) {
    for (T item : src) {
        dest.add(item);
    }
}
  • src —— producer(? extends T
  • dest —— consumer(? super T

使用方式:

List<Integer> ints = List.of(1, 2, 3);
List<Number> nums = new ArrayList<>();
copy(nums, ints); // OK:Number 是 Integer 的超型別

Stream API 與 wildcards

List<Integer> ints = List.of(1, 2, 3);
List<? extends Number> numbers = ints;

numbers.stream()
    .map(Number::doubleValue)
    .forEach(System.out::println);

搭配 wildcard 的篩選

public static void printAll(List<?> list) {
    for (Object o : list) {
        System.out.println(o);
    }
}

7. 常見錯誤

錯誤一:Raw types(原始型別)。
使用原始型別會關閉型別檢查,並導致執行階段錯誤。

List list = new ArrayList(); // raw type——不好!
list.add("字串");
list.add(123); // 可以加入任何東西

String s = (String) list.get(1); // ClassCastException!

切勿使用 raw types。請一律指定型別參數:List<String>List<Integer>

錯誤二:搭配 extends 的不安全操作。
嘗試向帶有 ? extends ... 的集合新增元素會導致編譯錯誤。

List<? extends Number> nums = new ArrayList<Integer>();
nums.add(3.14); // 編譯錯誤!

錯誤三:因型別擦除導致的多載衝突。
不能只依泛型參數來多載方法——型別擦除後簽章會相同。

public void process(List<String> list) { /* ... */ }
public void process(List<Integer> list) { /* ... */ } // 編譯錯誤!

錯誤四:泛型型別的陣列。
由於型別擦除,無法建立參數化型別的陣列。

List<String>[] arr = new List<String>[10]; // 編譯錯誤!
1
問卷/小測驗
集合介面,等級 27,課堂 4
未開放
集合介面
集合介面
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION