泛型通配符与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> 的子类
虽然 String 是 Object 的子类,但 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); // ✅
上界通配符 ` extends T>` —— 只能读
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
下界通配符 ` super T>` —— 只能写
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。
| 你做什么 | 用哪个 | 为什么 |
|---|---|---|
| 只从中读数据 | ` extends T>` | 每个元素至少是 T,安全读取 |
| 只往里写数据 | ` 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);
}