Collections.copy方法体现PECS原则:src为生产者(? extends T),dst为消费者(? super T),签名明确界定读写边界。

Java中Collections.copy方法是PECS原则最典型、最干净的落地体现——它不靠文档解释,而是用方法签名本身把“谁读、谁写、读什么、写到哪”全说清楚。
src 是生产者: extends T> 保证安全读取
源列表声明为 List<? extends T>,意味着它只负责“产出”T或其子类的实例:
- 可以安全遍历并取出每个元素,编译器保证它们都能向上转型为T(比如从
List<Apple>读出的一定是Fruit或其子类) - 禁止调用
add()(除null),因为底层可能是List<Apple>也可能是List<Banana>,写入任意具体子类都可能破坏类型一致性 - 这种设计解耦了调用方:传
List<Student>、List<Person>甚至List<Object>(若T是Object)都合法,只要元素能当T用
dest 是消费者: super T> 保证安全写入
目标列表声明为 List<? super T>,意味着它只负责“接收”T或其子类的实例:
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
- 可以安全调用
set(i, element)或add(element),因为T一定能放进List<T>、List<Object>甚至List<Serializable>(只要T实现该接口) - 读取操作受限:从
dest.get(i)只能得到Object,不能直接当T用——这不是缺陷,而是明确告诉调用方:“我这里装得宽,但取出来时你得自己判断” - 这种设计让目标容器更包容:一个
ArrayList<Object>或LinkedList<Serializable>都能作为dest接收String、Integer等T实例
二者成对出现,构成类型安全的数据管道
PECS不是孤立规则,而是一组协作契约:
立即学习“Java免费学习笔记(深入)”;
-
src产出的每个元素,类型上属于T及其子类;dest声明能容纳T及其父类——两者交集就是T,确保“产”和“消”在类型层面严丝合缝 - 整个复制过程无需强制转换、不抛
ClassCastException、不依赖运行时类型检查,所有安全性由编译器静态验证 - 典型调用如
copy(students, people)(Student→Person)、copy(employees, objects)(Employee→Object)全部通过,且语义清晰
实际使用时的关键约束
签名再精妙,也绕不开运行时前提:
- dest必须预先具备足够容量(长度 ≥ src.size()),否则抛
IndexOutOfBoundsException - dest不能是不可变列表(如
Arrays.asList()返回的固定大小列表),否则set()会抛UnsupportedOperationException - 它只适用于List,不支持Set/Map等其他集合——因为copy本质是按索引覆盖,依赖有序+可随机写


















