一句话记住

PECS:Producer Extends, Consumer Super

  • 往外读 → extends
  • 往里写 → super

核心概念:箱子的只读/只写

把泛型容器想象成箱子

通配符记忆口诀箱子特性你能做什么
<? extends T>extends = 读取里面可能是 T 的任何子类只能拿出来(读),不能放进去
<? super T>super = 写入里面可以装 T 的任何父类只能放进去(写),拿出来只能是 Object

<? extends T> - 上界通配符

含义:这个箱子装的是 T 或 T 的子类,但具体是什么不知道

1
2
3
4
5
6
7
8
List<? extends Number> list = new ArrayList<Integer>();

// ✅ 可以读 - 拿出来肯定是 Number
Number n = list.get(0);

// ❌ 不能写 - 不知道箱子实际是 Integer、Double 还是其他
// list.add(10); // 编译错误!
// list.add(3.14); // 编译错误!

为什么不能写?
list 实际可能是 ArrayList<Integer>,你往里面塞个 Double,运行时就会爆炸。编译器直接禁止。

<? super T> - 下界通配符

含义:这个箱子装的是 T 或 T 的父类,你可以安全地放入 T

1
2
3
4
5
6
7
8
9
List<? super Number> list = new ArrayList<Object>();

// ✅ 可以写 - Integer、Double 都是 Number 的子类,肯定能装
list.add(10);
list.add(3.14);

// ❌ 不能读成具体类型 - 可能是 Object,可能是 Number
// Number n = list.get(0); // 编译错误!
Object obj = list.get(0); // 只能当成 Object

为什么不能读成 Number?
list 实际可能是 ArrayList<Object>,里面装个 String 也是合法的,你当成 Number 用就会崩溃。

PECS 实战

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 生产者:从集合读取数据 → 用 extends
public void printNumbers(List<? extends Number> list) {
for (Number n : list) {
System.out.println(n);
}
}

// 消费者:往集合写入数据 → 用 super
public void fillNumbers(List<? super Number> list) {
list.add(100);
list.add(3.14);
}

// 同时使用
public void copy(List<? extends Number> src, List<? super Number> dst) {
for (Number n : src) {
dst.add(n);
}
}

对比总结

1
2
3
4
5
6
7
8
9
10
11
12
13
List<Integer> integers = Arrays.asList(1, 2, 3);

// extends - 只读
List<? extends Number> readOnly = integers;
Number n = readOnly.get(0); // ✅
// readOnly.add(10); // ❌ 编译错误

// super - 只写(读出来是 Object)
List<? super Number> writeOnly = new ArrayList<Object>();
writeOnly.add(100); // ✅
writeOnly.add(3.14); // ✅
// Number n = writeOnly.get(0); // ❌ 编译错误
Object o = writeOnly.get(0); // ✅
场景选用
只读不写<? extends T>
只写不读<? super T>
又读又写不用通配符,直接用 List<T>

速查表

1
2
3
4
5
读取数据 → extends → Producer
写入数据 → super → Consumer

extends: 只能 get,不能 add(除了 null
super: 只能 add,get 出来是 Object