一句话记住

PECS:Producer Extends, Consumer Super

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

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

把泛型容器想象成箱子

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

<? extends T> - 上界通配符

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

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

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 实战

// 生产者:从集合读取数据 → 用 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);
    }
}

对比总结

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>

速查表

读取数据 → extends → Producer
写入数据 → super   → Consumer

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