PECS原则指导泛型通配符选择:只读用? extends T(生产者),只写用? super T(消费者),读写兼有则改用泛型方法;误用如强转或混用读写将导致编译失败。

PECS 原则是 Java 泛型通配符使用的实践指南,核心是按参数在方法中的“角色”选通配符:往外拿数据就用 ? extends T,往里塞数据就用 ? super T。它不看类继承图,只盯操作方向——读还是写。
只读集合:用 ? extends T(Producer)
当你从集合中取元素、遍历、做计算或调用其方法,且不修改集合内容时,它是生产者。
- 允许调用
get()、iterator().next(),返回值可安全赋给T或其父类型(如Fruit f = list.get(0)) - 禁止调用
add()、set(),哪怕传new T()也不行——编译器无法确认容器实际能接受哪种子类型 - 示例:
List<? extends Number>可接收ArrayList<Integer>或LinkedList<Double>,遍历时都能调用doubleValue(),但不能add(3.14)
只写集合:用 ? super T(Consumer)
当你向集合中添加元素、收集结果、批量归并输入,且不依赖读出的具体类型时,它是消费者。
- 允许调用
add(T)、addAll(),任意T实例(含子类对象)都能安全加入 - 禁止将
get(0)直接当T用,只能接成Object——因为容器可能是List<Object>或List<Serializable>,上界不确定 - 示例:
Collections.<Integer>addAll(dest, src)中,dest声明为Collection<? super Integer>,这样ArrayList<Number>和LinkedList<Object>都能作为目标接收整数
读写都需:别用通配符,改用泛型方法
如果一个参数既要读又要写,通配符无法兼顾安全与灵活,说明设计需要调整。
立即学习“Java免费学习笔记(深入)”;
- 直接使用具体类型(如
List<String>)或泛型方法参数(如<T> void swap(List<T> list, int i, int j)) - 避免写类似
void process(List<?> list)然后又试图list.add(...)的代码——编译器会拒绝 - 标准库中的
Collections.copy(src, dest)就是典型拆分:src 是List<? extends T>,dest 是List<? super T>
常见误用与避坑点
这些错误看似合理,实则违反类型安全逻辑:
-
List<? extends String> list = new ArrayList<>(); list.add("hi");→ 编译失败。即使String是 final 类无子类,通配符规则仍生效:只读不许写 - 把
List<Apple>强转成List<Fruit>→ 编译报错。Java 泛型不变,必须靠? extends Fruit来适配读取场景 - 用
? super T后又写Student s = list.get(0);→ 编译失败。只能声明为Object o = list.get(0);,否则可能运行时抛ClassCastException


















