并行分片归集必须用 PECS 是因为 Collections.copy(List<? super T> dest, List<? extends T> src) 签名天然适配:src 产出子类型(如 List<PaymentEvent>),dest 接收父类型(如 List<BusinessEvent>),编译期确保类型安全,避免强转、GC 开销与运行时错误。

直接用符合 PECS 原则的泛型拷贝函数,能安全、高效地把不同子类数据批量写入更宽泛类型的集合中,特别适合在并行分片处理后统一归集结果的场景。
为什么并行分片归集必须用 PECS
海量业务数据常被拆成多个子任务并行处理(如按用户 ID 分片),每个线程产出的是具体子类型数据(如 OrderDetail),但最终要汇总进一个统一容器(如 List<businessevent></businessevent> 或 List<object></object>)。若不用 PECS:
- 硬写
List<businessevent> dest, List<orderdetail> src</orderdetail></businessevent>→ 编译失败,泛型不协变 - 退而求其次用
List<object></object>接收 → 失去类型约束,后续处理需反复 instanceof + 强转 - 手动 new 中间 list 再遍历转换 → 增加 GC 压力,违背“零拷贝”优化目标
标准 copy 方法签名就是为并行归集设计的
Collections.copy(List<? super T> dest, List<? extends T> src) 这个签名天然适配分片-归集流程:
-
src是生产者:每个分片线程产出List<? extends BusinessEvent>(比如List<PaymentEvent>、List<RefundEvent>) -
dest是消费者:主控线程提供List<? super BusinessEvent>(如CopyOnWriteArrayList<BusinessEvent>,线程安全且支持并发 add) - 编译器自动校验:只要
PaymentEvent是BusinessEvent的子类,调用就合法,无需运行时检查
实战:并行分片 + PECS 归集的典型代码结构
以电商订单事件分片处理为例:
- 定义统一基类:
abstract class BusinessEvent { long timestamp; } - 子类分片处理:
List<PaymentEvent> payments = processPayments(shard); - 线程安全归集容器:
List<BusinessEvent> allEvents = new CopyOnWriteArrayList<>(); - 单行完成归集:
Collections.copy(allEvents, payments);(或自定义泛型方法) - 多分片可并行执行:
executor.invokeAll(tasks),每个 task 内部调用 copy,互不干扰
比原始类型或强制转换更可靠
有人倾向用 List<Object> 当中转容器,但这样会丢失语义:
-
allEvents.get(0)只能当Object用,后续仍要(BusinessEvent)强转 - 若误塞入非事件对象(如
new Date()),编译期无法拦截 - 而
List<? super BusinessEvent>明确承诺:“我只收 BusinessEvent 及其子类”,编译器全程把关


















