PECS原则明确指导泛型通配符使用:Producer Extends(只读时用? extends T,确保安全取值)、Consumer Super(只写时用? super T,确保安全注入);其本质是按角色选通配符,非按继承方向,违背则破坏类型安全。

PECS(Producer Extends, Consumer Super)是 Java 泛型中关于通配符使用的核心原则,它指导我们何时用 ? extends T、何时用 ? super T。在 MyBatis-Plus 的 AbstractWrapper 及其子类中,这一原则虽未显式出现在公开 API 签名里,却深刻体现在设计逻辑和实际用法中——尤其在字段引用、泛型参数传递与类型安全构造环节。
Wrapper 中的“Consumer”角色:字段名或 Lambda 表达式作为输入源
AbstractWrapper 本身是一个条件“消费者”:它接收字段信息(字符串或方法引用),并据此生成 SQL 片段。这类输入操作符合 Consumer Super 的语义——即 Wrapper 需要能接受更宽泛类型的输入(比如父类字段、通用函数式接口),以保证灵活性。
- 普通
QueryWrapper<User>的eq("name", "张三")中,"name"是字符串字面量,属于最基础的输入,无需泛型约束,但设计上允许任意String,本质是Consumer<? super String>的体现(虽然没写出来)。 - 而
LambdaQueryWrapper<User>的eq(User::getName, "张三"),其第一个参数类型为SFunction<User, ?>(MP 自定义函数接口)。这里的?>允许返回任意类型(如String、Integer),正是为了适配不同字段——它不产出具体值,而是被 Wrapper “消费”来提取列名,所以用的是上界宽松的泛型,契合 Consumer Super 思想。
Wrapper 中的“Producer”角色:SQL 片段与参数映射的输出
当 Wrapper 构建完成,调用 getSqlSegment() 或 getParamNameValuePairs() 时,它开始“产出”结果。这些方法返回的类型具有明确上界,属于典型的 Producer Extends 场景:
-
getSqlSegment()返回String,不可变、只读,是纯粹的产出; -
getParamNameValuePairs()返回Map<String, Object>,其中 value 可能是各种具体类型(String、Integer、LocalDateTime等),但对外统一视为Object—— 这相当于Map<String, ? extends Object>的简化表达,使用者只能读取,不能往里 put 未知子类型,保障类型安全。
allEq 方法里的 PECS 实际体现
allEq 是最能体现 PECS 的典型方法。它的签名之一为:
立即学习“Java免费学习笔记(深入)”;
allEq(BiPredicate<String, Object> filter, Map<String, Object> params)
这里:
-
params是输入源(Consumer):Map 的 value 被 Wrapper 消费来生成条件,所以 value 类型设为Object(即? super X的等效),兼容所有字段值; -
filter是一个BiPredicate<String, Object>,其中第二个参数Object同样是宽泛输入,便于判断是否纳入条件; - 而整个方法不返回泛型集合,只改变自身状态,因此不涉及生产侧的 extends 约束,但设计上已为后续的 SQL 产出预留了类型一致性。
为什么不用 extends?什么时候该警惕
如果你试图把 QueryWrapper<User> 声明为 QueryWrapper<? extends User>,编译会失败——因为 Wrapper 需要确切知道实体类型才能解析字段、生成 SQL。它不是用来“产出” User 实例的容器,而是“消费” User 结构来构建条件的工具。强行用 extends 会导致无法调用 eq 等方法(类型擦除后无法确认字段是否存在),违背其 Consumer 本质。
真正该用 extends 的地方,反而是你封装通用查询逻辑时:比如写一个 <T> List<T> selectByCondition(Class<T> clazz, Wrapper<T> wrapper),这里的 T 是产出结果类型,才需考虑上界约束。


















