Z
ZHANK
编程在线教程Java 教程泛型通配符与PECS
泛型

泛型通配符与PECS

掌握 <?>、<? extends T>、<? super T> 与 PECS 原则

泛型通配符与 PECS

泛型有个反直觉的特性:List<String> 不是 List<Object> 的子类。这导致很多看似合理的代码编译不过。通配符 ? 就是来解决这个问题的。

学完本章你将: 掌握 <?><? extends T><? super T> 的含义与 PECS 原则。


问题:泛型不协变

先理解为什么需要通配符。假设你写了个打印列表的方法:

java
// 期望:能打印任何类型的 List
public static void printList(List<Object> list) { // ❌ 不行!
    for (Object obj : list) System.out.println(obj);
}

List<String> names = List.of("A", "B");
printList(names); // ❌ 编译错误!List<String> 不是 List<Object> 的子类

虽然 StringObject 的子类,但 List<String> 不是 List<Object> 的子类。这种关系叫"泛型不变性"。通配符正是来打破这种限制的。


无界通配符 `` —— 什么都能读

List<?> 表示"某种未知类型的 List"。可以从中读数据(当 Object 读),但不能往里写(除了 null),因为不知道具体类型。

java
public static void printList(List<?> list) {
    for (Object obj : list) {  // 读出来是 Object
        System.out.println(obj);
    }
    // list.add("X");  // ❌ 不能写!不知道 list 到底装什么类型
}

List<String> strings = List.of("A", "B");
List<Integer> nums = List.of(1, 2, 3);
printList(strings); // ✅ 现在可以了
printList(nums);    // ✅

上界通配符 `` —— 只能读

List<? extends Number> 表示"元素是 Number 或其某个子类 的 List"。因为每个元素至少是 Number,所以可以安全地读(当 Number 读)。但你不能往里加东西,因为不知道具体是 Integer 还是 Double。

java
// 计算数字列表的总和 —— 适用于 Integer、Double 等所有数字类型
public static double sum(List<? extends Number> nums) {
    double total = 0;
    for (Number n : nums) {
        total += n.doubleValue(); // ✅ 能读:每个元素至少是 Number
    }
    return total;
    // nums.add(10);     // ❌ 不能写:可能是 List<Double>,不能加 Integer
    // nums.add(3.14);   // ❌ 也不能:可能是 List<Integer>,不能加 Double
}

sum(List.of(1, 2, 3));       // 6.0
sum(List.of(1.5, 2.7, 3.2)); // 7.4

下界通配符 `` —— 只能写

List<? super Integer> 表示"元素是 Integer 或其某个父类 的 List"。因为 Integer 肯定是其中的一员,所以可以安全地添加 Integer。但读出来的类型不确定(可能是 Number、Object),只能当 Object 用。

java
// 往列表里填数字 —— 适用于 List<Integer>、List<Number>、List<Object>
public static void fill(List<? super Integer> list) {
    for (int i = 1; i <= 5; i++) {
        list.add(i); // ✅ 能写:Integer 肯定是合法元素
    }
    // Integer n = list.get(0); // ❌ 读出来是 Object,不是 Integer
}

List<Number> nums = new ArrayList<>();
List<Object> objs = new ArrayList<>();
fill(nums); // ✅ List<Number> 可以放 Integer
fill(objs); // ✅ List<Object> 也可以放 Integer

PECS 原则 —— 记住这八个字

Producer Extends,Consumer Super 生产者(往外拿数据)用 extends,消费者(往里放数据)用 super

你做什么用哪个为什么
只从中读数据``每个元素至少是 T,安全读取
只往里写数据``T 一定是合法元素,安全写入
既要读又要写不用通配符,用 ``通配符不能同时读写

经典案例 —— Collections.copy() 就是 PECS 的完美示范:

java
// src 是"生产者"(从里面取数据)→ extends
// dest 是"消费者"(往里面放数据)→ super
public static <T> void copy(
    List<? super T> dest,    // Consumer — super
    List<? extends T> src    // Producer — extends
) {
    for (T item : src) dest.add(item);
}