pecs原则是泛型集合读写边界的编译期保障:只读用? extends t(生产者,可安全取t及子类,禁止add除null),只写用? super t(消费者,可安全add t及子类,取值仅object)。

PECS 原则(Producer Extends, Consumer Super)不是技巧,而是对集合“使用意图”的类型化表达。它直接回应面向对象中常见的类型转换困境:比如想把 List<student></student> 安全传给一个处理 Person 的方法,又不想强转、不希望编译器放行危险操作,更不能靠运行时 ClassCastException 来兜底——PECS 就是在编译期把读写边界划清楚。
只读场景:用 ? extends T 避免向上转型失败
当你只从集合里取数据,并当作 T 或其父类 使用时,? extends T 让编译器确认“拿出来的东西肯定能当 T 用”。
- 例如:
void printNames(List extends Person> people)可接收List<student></student>、List<teacher></teacher>,因为它们都产出Person或子类实例 - 你可以安全调用
people.get(0).getName()(假设getName()在Person中定义),因为取出的对象一定是Person或其子类 - 但禁止
people.add(new Student())—— 编译器不知道实际是List<student></student>还是List<teacher></teacher>,加进去可能破坏类型一致性 - 唯一允许的写入是
null,它在所有引用类型中都合法
只写场景:用 ? super T 避免向下转型风险
当你主要往集合里塞数据,且这些数据是 T 或其子类,但读出来只按 Object 处理或根本不读,? super T 放宽了输入兼容性。
- 例如:
void collectGrades(Collection super Student> target)可接收Collection<student></student>、Collection<person></person>,甚至Collection<object></object> - 你可以安全调用
target.add(new Graduate())(Graduate是Student子类),因为所有目标集合都能容纳Student及其子类 - 但从中读出的元素只能当
Object用;若强行(Student) target.iterator().next(),可能抛异常——因为实际可能是Collection<object></object>
流转组合:上下游各守其界,避免中间强转
真实业务中,数据常从一种集合流向另一种。PECS 成对使用,让拷贝、过滤、聚合等操作既类型安全又无需擦除或包装。
- 通用拷贝方法:
public static <t> void copy(List extends T> src, List super T> dest)</t> - 调用示例:
copy(students, persons)(Student → Person)、copy(people, objects)(Person → Object)都合法 - 核心逻辑
dest.add(item)能成立,是因为src保证产出T,dest保证能接收T - 若统一用
List<t></t>,你就得写new ArrayList<person>(students)</person>或做类型投影,徒增开销和出错点
避开典型误用:类型安全不是靠运气
PECS 不是让代码“看起来泛型”,而是防止两类错误:
- 误用
? extends T写数据 → 编译失败,提前暴露设计矛盾 - 误用
? super T读数据并强转 → 编译通过但运行时可能崩溃,因为读出的实际类型比预期更宽泛 - 用无界通配符
?替代 PECS → 读写都被禁(除null),失去实用性 - 忽略“只读/只写”意图,硬套通配符 → 最终还是得靠
@SuppressWarnings("unchecked"),等于放弃泛型价值










