PECS 是 Producer Extends, Consumer Super 的缩写,是 Java 泛型中解决通配符(?)使用歧义的核心规范,用来明确:
泛型集合/容器,到底该用 ? extends T 还是 ? super T

它的本质只有一句话:
只读用 extends(生产者),只写用 super(消费者),既读又写用精确泛型 T


一、先搞懂核心概念

1. 两个关键定义(PECS 的基础)

  1. 生产者(Producer)
    只从容器里拿数据,容器向外输出数据 → 像工厂生产产品。
  2. 消费者(Consumer)
    只往容器里放数据,容器接收外部数据 → 像用户消费产品。

2. 两个通配符

写法 含义 限制
? extends T T 及其所有子类(上界通配符) 只能读,不能写(除了 null)
? super T T 及其所有父类(下界通配符) 只能写,不能安全读(除了 Object)

二、PECS 核心规则

规则 1:生产者 → 使用 ? extends T

适用场景:你需要从集合中读取元素,不需要添加元素。
特点:安全读取,类型是 T 或其子类型;无法添加任何元素(除 null)

规则 2:消费者 → 使用 ? super T

适用场景:你需要往集合中添加元素,不需要读取元素。
特点:安全添加 T 及其子类型;读取只能得到 Object

规则 3:既读又写 → 不用通配符,直接用 T

✅ 如果既要存又要取,不要用 ? 通配符,用精确泛型。


三、代码实战:一看就懂

我们用基础类结构演示:

class Fruit {}
class Apple extends Fruit {}
class Banana extends Fruit {}

1. 生产者:? extends T(只读)

// 生产者:只能读取 Fruit 及其子类
public static void printFruits(List<? extends Fruit> list) {
    // ✅ 安全读取:取出的一定是 Fruit 或其子类
    for (Fruit fruit : list) {
        System.out.println(fruit);
    }

    // ❌ 编译报错:无法添加任何元素(编译器不知道具体类型)
    // list.add(new Apple());
    // list.add(new Fruit());
    list.add(null); // 唯一允许:添加 null
}

✅ 调用:

List<Apple> apples = new ArrayList<>();
List<Fruit> fruits = new ArrayList<>();
printFruits(apples);  // 合法
printFruits(fruits);  // 合法

2. 消费者:? super T(只写)

// 消费者:只能存入 Fruit 及其子类
public static void addApples(List<? super Fruit> list) {
    // ✅ 安全添加:Fruit / Apple / Banana 都能放
    list.add(new Fruit());
    list.add(new Apple());

    // ❌ 不安全读取:只能用 Object 接收(丢失类型)
    // Fruit fruit = list.get(0);  编译报错
    Object obj = list.get(0); // 唯一安全读取方式
}

✅ 调用:

List<Fruit> fruits = new ArrayList<>();
List<Object> objects = new ArrayList<>();
addApples(fruits);   // 合法
addApples(objects);  // 合法

四、为什么必须遵守 PECS?(底层原理)

1. 为什么 ? extends 不能写?

List<? extends Fruit> 可能是 List<Apple>List<Banana>
编译器不知道具体是哪个子类,不敢让你添加任何元素,否则会类型不安全。

比如你往 List<Apple> 里加 Banana,会破坏类型。

2. 为什么 ? super 不能安全读?

List<? super Fruit> 可能是 List<Fruit>List<Object>
读取时编译器只能确定是 Object,无法确定具体类型。


五、经典使用场景:Java 集合工具类

Java 源码里大量使用 PECS,比如 Collections

// 复制:src 是生产者(只读),dest 是消费者(只写)
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
    for (int i = 0; i < src.size(); i++)
        dest.set(i, src.get(i));
}
  • src:只读取 → ? extends T
  • dest:只写入 → ? super T

这就是 PECS 最标准的工程实践。


六、快速判断口诀

面对一个泛型参数,问自己两个问题:

  1. 我是要从里面拿数据吗?
    → 生产者 → ? extends T
  2. 我是要往里面放数据吗?
    → 消费者 → ? super T
  3. 既要拿又要放?
    → 不用通配符 → 直接用 T

七、避坑指南

  1. ? extends T 不能添加任何非 null 值,否则编译报错。
  2. ? super T 读取只能得到 Object,不适合读取场景。
  3. 通配符不能作为返回值类型,返回值用精确泛型。
  4. PECS 只针对集合/容器,单个对象直接用继承即可。

总结

  1. PECS = Producer Extends, Consumer Super
  2. 只读(生产者)? extends T
  3. 只写(消费者)? super T
  4. 既读又写 → 不用通配符,直接用 T
  5. 遵守 PECS 能让泛型代码更安全、更灵活、兼容更多类型
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐