PECS原则核心是按操作意图选通配符:只读用? extends T(生产者),确保get()返回T且禁止add();只写用? super T(消费者),确保add(T)安全但get()仅返回Object。

PECS原则的核心是用通配符边界匹配操作意图:只读时用 ? extends T,只写时用 ? super T。它不解决“能不能运行”,而是确保编译期类型安全——读得出来、写得进去,且不会因多态误用引发 ClassCastException。
什么时候该用 ? extends T(生产者场景)
当你只从集合中取数据,且希望所有元素都能被当作 T 或其父类型安全使用时,就属于生产者。
- 允许调用 get(),返回值可安全赋给 T 类型变量(例如 Fruit f = list.get(0))
- 禁止调用 add()(除 null 外),因为实际类型可能是 Apple、Banana 等任意子类,编译器无法验证写入合法性
- 常见用途:工具方法接收多种子类型集合做统一处理,如 printFruits(List extends Fruit>)、sumOfList(List extends Number>)
什么时候该用 ? super T(消费者场景)
当你只向集合中放数据,且需要保证能存下 T 及其所有子类实例时,就属于消费者。
- 允许调用 add(),传入 T 或其任意子类对象都安全(例如 list.add(new Apple()))
- get() 返回类型只能是 Object,无法直接转为 T(例如 Apple a = list.get(0) 编译失败),因为实际类型可能是 Fruit 或 Object
- 常见用途:收集器、缓冲区、回调入参,如 feed(List super Dog>, Dog)、copy(List super T>, List extends T>)
为什么不能混用读写?
泛型默认不变(invariant),而通配符通过牺牲部分能力换取类型灵活性:
- ? extends T 知道上界(≤ T),所以读安全;但下界未知(可能是 Apple、Orange…),写不安全
- ? super T 知道下界(≥ T),所以写安全;但上界未知(可能是 Fruit、Object…),读只能得 Object
- 若既要读又要写,就别用通配符,直接用具体类型,如 List
一个典型协同示例
复制数据时 source 是生产者、target 是消费者:
public static <t> void copy(List super T> target, List extends T> source) {
for (T item : source) {
target.add(item); // ✅ source 能产出 T,target 能消费 T
}
}</t>
调用时可传 copy(fruitList, appleList) 或 copy(numberList, integerList),类型完全对齐。










