PECS原则在Builder模式中需按参数角色选择通配符:接收只读集合用? extends T,函数式参数按消费/生产角色选? super T或? extends T,多层泛型需逐层判断。

Java 中 PECS 原则在链式调用 Builder 模式参数传递中,不是直接“写在 Builder 类里”的硬性规则,而是当你通过 Builder 构建泛型对象、并接受外部集合或函数式参数时,必须主动应用的类型设计逻辑。它解决的核心问题是:如何让 Builder 既能安全接收各种兼容类型的输入(比如 List extends Fruit>),又能正确地把数据写入内部结构(比如 List<fruit></fruit> 或 List super Apple>)。
下面从三个实际场景讲清楚怎么用:
Builder 接收只读集合参数时用 ? extends T
当 Builder 提供一个 .fruits(List extends Fruit> list) 方法,目的是把传入的水果列表“读出来”添加到内部 List<fruit></fruit> 中,那就该用 extends——因为这是典型的生产者角色(你从 list 里取元素,不往里写)。
- ✅ 允许传入
List<apple></apple>、List<banana></banana>、List<fruit></fruit> - ❌ 不允许调用
list.add(new Apple())(编译报错,符合只读预期) - ✅ 内部可安全执行
for (Fruit f : list) { this.fruits.add(f); }
public class JuiceBuilder {
private final List<fruit> fruits = new ArrayList();
public JuiceBuilder fruits(List extends Fruit> src) {
src.forEach(this.fruits::add); // 安全读取,自动向上转型为 Fruit
return this;
}
}</fruit>
Builder 接收函数式参数时按角色选通配符
如果 Builder 支持自定义校验逻辑,比如 .validator(Consumer super Fruit> validator),这里 validator 是消费者(它要消费 Fruit 及其子类),所以用 super。
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
- ✅ 可传入
Consumer<fruit></fruit>、Consumer<object></object>,甚至Consumer<apple></apple>(注意:Consumer<apple></apple>不能处理Banana,所以更常用Consumer<fruit></fruit>或Consumer<object></object>) - ✅ 调用时
validator.accept(new Apple())合法(Apple是Fruit的子类,满足? super Fruit) - ❌ 不能从
validator里“取出”类型(Consumer没有 get 方法,但原理一致:只写不读)
public class JuiceBuilder {
private Consumer super Fruit> validator = f -> {};
public JuiceBuilder validator(Consumer super Fruit> v) {
this.validator = v;
return this;
}
public Juice build() {
this.fruits.forEach(validator); // Apple/Banana/Fruit 都能被 accept
return new Juice(fruits);
}
}
多层泛型嵌套时,PECS 要逐层判断
Builder 本身带泛型(如 class <t> DataBuilder<t></t></t>),又接受 Function<list extends t>, String></list> 类型的格式化器,这时:
-
List extends T>:是生产者(你从 list 里读T或其子类)→ 用extends -
Function的入参是List extends T>,出参是String,整个Function是消费者(它消费 list)→ 但Function自身的泛型参数已由Function<r t></r>规则决定,不用额外加super
关键点:每个泛型位置独立判断它是生产者还是消费者,不因外层是 Builder 就统一用某一种。
public class DataBuilder<t> {
private List<t> data = new ArrayList();
public <r> DataBuilder<t> formatter(Function<list extends t>, R> fn) {
R result = fn.apply(data); // 安全读取 data,fn 是消费者,但它的输入是生产者
return this;
}
}</list></t></r></t></t>
不复杂但容易忽略
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










