在阅读和调试 Java 源码时,我们习惯了优雅的链式写法,比如使用 MyBatis-Plus 构建查询条件时,代码通常看起来是这样的:
LambdaQueryWrapper<BizSnapshot> queryWrapper = Wrappers.<BizSnapshot>lambdaQuery() .eq(ObjectUtil.isNotEmpty(stateMachineName), BizSnapshot::getStateMachineName, stateMachineName)
.eq(ObjectUtil.isNotEmpty(bizId), BizSnapshot::getBizId, bizId) .eq(ObjectUtil.isNotEmpty(state), BizSnapshot::getState, state) .gt(BizSnapshot::getDbShardId, 0) .orderByDesc(BizSnapshot::getCreateTime)
.last("limit 1");
非常简洁,逻辑清晰,一看就是在动态拼装 SQL 查询条件。
然而,当我在调试项目运行逻辑、追踪到 .class
文件(比如在 IDEA 的反编译视图里),却惊奇地发现这段代码被“变成了”如下怪样子:
LambdaQueryWrapper<BizSnapshot> queryWrapper = (LambdaQueryWrapper) ((LambdaQueryWrapper) ((LambdaQueryWrapper) ((LambdaQueryWrapper) ((LambdaQueryWrapper) ((LambdaQueryWrapper) Wrappers.lambdaQuery() .eq(...) ) .eq(...) ) .eq(...) ) .gt(...) ) .orderByDesc(...) ) .last("limit 1");
是不是有点头晕?我当时也感到很迷惑:我代码根本不是这么写的啊?是谁偷偷给我加了这些括号和强制类型转换?🤯
🔍 原因揭秘:反编译造成的“幻觉”
这并不是你写错了,也不是 MyBatis-Plus 框架内部“奇怪”的代码生成。
真正的原因是:
Java 编译器在编译链式调用时,会将每一步调用都编译成中间变量和返回值;而反编译工具在还原代码时,为了确保类型正确性,往往会“显式地”对每一步调用的返回值进行强制类型转换。
换句话说:你看到的那堆 (LambdaQueryWrapper)
强转,其实是反编译器为了保证类型安全地还原链式调用逻辑,而不得不做出的“多余”修饰。
📦 举个简单例子:链式 set 方法
比如我们有一个 POJO:
public class Person {private String name;private int age; public Person setName(String name) { this.name = name; return this; } public Person setAge(int age) { this.age = age; return this; }
}
源码中,我们可以这么写:
Person p = new Person().setName("Tom").setAge(18);
但反编译后,你可能会看到:
Person p = (Person)((Person)(new Person()).setName("Tom")).setAge(18);
是不是和 MyBatis-Plus 的查询构造很像?这就是编译-反编译机制的真实面貌。
🧪 MyBatis-Plus 为何会被强转?
原因是:
-
Wrappers.lambdaQuery()
返回的是LambdaQueryWrapper<T>
。 -
每个
.eq()
、.gt()
、.orderByDesc()
方法实际定义在其父类AbstractLambdaWrapper
中。 -
反编译工具看不到泛型信息、也不清楚链式调用上下文,所以它“保守起见”地认为需要每一步都强转成
LambdaQueryWrapper
,确保你能继续调用.last()
等方法。
而我们在源码中,不需要这么写,是因为 Java 的类型推断和泛型信息仍然保留在源码编译过程中,IDE 可以推导出你写的是哪个类的方法。
✅ 写法建议
所以,我们实际写代码时根本不需要手动加括号和强转:
LambdaQueryWrapper<BizSnapshot> wrapper = Wrappers.lambdaQuery(BizSnapshot.class) .eq(...) .gt(...) .orderByDesc(...) .last("limit 1");
这种写法才是推荐的、简洁且清晰的。
🧠 总结
-
反编译出来的
(LambdaQueryWrapper)((LambdaQueryWrapper)...
是工具生成的,不是真正的源码写法。 -
不需要模仿或担心这类反编译结果。
-
编译器很聪明,能帮我们正确推导类型;反编译器太保守,才加了这些“保险括号”。
🗂️ 参考
-
MyBatis-Plus 官方文档
-
ObjectUtil.isNotEmpty 来自 Hutool 工具库
-
Java 编译器链式调用机制解析
有类似疑问的朋友,不妨也去试试看 javap -c
或者用 IDEA 的 Decompiler 看看 class 文件是如何被“翻译”的。