Java泛型中PECS原则指导通配符使用:生产者用? extends T(只读,如sum方法),消费者用? super T(只写,如addIntegers方法),组合使用如copy方法实现安全复用,需避免类定义滥用等误区。

在 Java 泛型中,用有限制的通配符(bounded wildcards)实现生产者-消费者模式,核心是遵循 PECS 原则:Producer Extends,Consumer Super。也就是说,如果一个泛型结构只产出(读取)数据,就用 ? extends T;如果只消费(写入)数据,就用 ? super T。这能保证类型安全,同时保留必要的灵活性。
用 ? extends T 作为生产者(只读)
当方法从容器中“获取”元素时,应使用上界通配符,确保能安全读取子类型实例。
- 声明为
List<? extends Number>的列表可以存放Integer、Double等,但不能向其中添加任何对象(除了null),因为编译器无法确定具体子类型。 - 适合场景:遍历、计算最大值、求和等只读操作。
- 示例:
public double sum(List<? extends Number> numbers) {
return numbers.stream().mapToDouble(Number::doubleValue).sum();
}
用 ? super T 作为消费者(只写)
当方法向容器中“放入”元素时,应使用下界通配符,允许写入 T 及其子类型。
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
- 声明为
List<? super Integer>的列表可以接收Integer、Long(若Long是Integer子类?注意:实际不是,但Integer可以写入List<Number>或List<Object>)——关键在于目标类型必须是Integer的父类。 - 适合场景:批量添加、过滤后收集结果等写入操作。
- 示例:
public void addIntegers(List<? super Integer> target, Integer... nums) {
for (Integer n : nums) target.add(n);
}
组合使用:泛型方法 + 通配符提升复用性
单独使用通配符受限于“不可变性”,而泛型方法可结合两者,在保证安全的同时支持读写分离。
立即学习“Java免费学习笔记(深入)”;
- 定义一个方法,接受生产者(
Iterable<? extends T>)和消费者(Collection<? super T>): - public <T> void copy(Iterable<? extends T> src, Collection<? super T> dest) {
for (T item : src) dest.add(item);
} - 调用时自动推断
T,比如从List<Integer>复制到ArrayList<Number>或LinkedList<Object>都合法。
常见误区与注意事项
误用通配符会导致编译失败或运行时隐患。
-
List<? extends Number>不等于List<Number>:前者不能添加任何非null元素,后者可以添加任意Number实例。 -
List<? super Integer>不能从中安全读取元素(只能当作Object接收),因为实际类型可能是List<Object>或List<Number>,返回类型不确定。 - 不要在类定义中滥用通配符(如
class Box<? extends T>是非法的),通配符只适用于变量、参数、返回值等使用位置。


















