PECS原则是解决Java泛型不变性导致API僵化的工程方案:作为生产者时用? extends T放宽读取来源(可安全获取T类型,不可add),作为消费者时用? super T放宽写入目标(可安全add T及其子类,get仅得Object),从而在保障类型安全前提下提升三方库兼容性。

PECS原则(Producer Extends, Consumer Super)不是语法糖,而是解决泛型不变性带来的API僵化问题的工程方案。它让三方库在不牺牲类型安全的前提下,接纳更宽泛的输入类型,从而天然适配不同业务中已有的数据结构。
为什么三方库必须用PECS?
Java泛型是不变的(invariant):List<Integer> 不是 List<Number> 的子类型,哪怕 Integer 是 Number 的子类。如果三方库方法硬写死参数为 List<T>,调用方就只能传完全匹配的类型——这在真实业务中几乎不可行。比如支付系统返回 List<PayOrder>,而风控模块期望接收 List<Order>(PayOrder 是 Order 子类),不支持通配符就只能手动复制转换,破坏性能与语义。
作为生产者:用 ? extends T 放宽读取来源
当库方法从入参中“读取”数据(如校验、聚合、序列化),应声明为接收生产者:
- 方法签名示例:void validate(Iterable<? extends Order> orders)
- 调用方可传入 List<PayOrder>、Set<RefundOrder> 或 Arrays.asList(new Order())
- 方法体内能安全调用 orders.iterator().next() 并当作 Order 使用,但不能往里 add —— 因为实际容器可能是只读的 ArrayList<Order> 或不可变集合
作为消费者:用 ? super T 放宽写入目标
当库方法向入参中“写入”数据(如填充结果、收集日志、批量落库),应声明为接收消费者:
- 方法签名示例:<T> void fetchOrders(String query, Collection<? super Order> target)
- 调用方可传入 List<Object>、ArrayList<Order>,甚至 LinkedList<Serializable>
- 方法体内能安全调用 target.add(new PayOrder())(因为 PayOrder 是 Order 子类),但取元素时只能按 Object 接收 —— 类型下界决定了读取上限
组合使用:兼顾读写灵活性
复杂操作常需同时读源、写目标。例如一个通用转换器:
- <S, T> void transform(Iterable<? extends S> src, Collection<? super T> dst, Function<S, T> mapper)
- src 支持任何 S 及其子类集合(如 Iterable<UserV1> → S=User)
- dst 支持任何 T 及其父类集合(如 ArrayList<Entity> ← T=UserDTO)
- 业务无需为每次升级DTO或调整领域模型而修改库调用代码
真正提升兼容能力的,不是通配符本身,而是设计时明确区分“谁提供数据、谁接收数据”。PECS把类型约束权交给调用方,库只承诺行为契约 —— 这正是成熟三方库(如 Guava、Jackson、JDBC ResultSet)长期稳定的底层逻辑。

















