Java 记录类 record 和 Lombok 该怎么选?
JDK 16 及以上且只需不可变数据载体时优先用 record,JDK 8/11、需要可变对象或 JPA 实体时继续用 Lombok,二者可组合使用如 record 搭配 @Builder。
**结论先行:JDK 16 及以上、并且这个类只是"不可变数据载体",直接用 record,不要引入 Lombok;JDK 8/11 项目、需要可变对象或 JPA 实体,继续用 Lombok;两者并不互斥,record + Lombok @Builder 是常见组合。**
record 和 Lombok 的核心区别是什么?
结论:record 是语言级特性,编译期就确定了语义;Lombok 是编译期注解处理器,本质是"帮你生成代码"。
record 在 Java 14 作为预览特性(JEP 359)出现,Java 15 二次预览(JEP 384),Java 16 正式转正(JEP 395,2021 年 3 月发布)。它隐式继承 java.lang.Record,组件字段全部是 private final,自动生成全参构造器、访问器、equals、hashCode、toString。注意访问器是 name() 而不是 getName(),这是最容易踩的一脚。
Lombok 的 @Value 也能做到"final 字段 + 全参构造 + equals/hashCode/toString",但生成的是 JavaBean 风格的 getName(),并且需要 IDE 装插件、构建配置 annotationProcessor。一句话区分:**record 是不可变数据类型的官方语法,Lombok 是给普通类批量生成样板代码的工具。**
什么情况下应该用 record?
结论:JDK ≥ 16、纯数据承载、不需要 setter、不需要继承别的类,就选 record。
典型场景:DTO、API 返回值、方法内部的多返回值、Map 的复合 key、事件对象。写起来极短:
public record Point(int x, int y) {
public Point { // 紧凑构造器,做校验
if (x < 0) throw new IllegalArgumentException("x < 0");
}
}
生态兼容性上:Jackson 从 2.12(2020 年 11 月)开始原生支持 record 的序列化与反序列化;MyBatis 由于 record 没有无参构造和 setter,必须用 resultMap 的 <constructor> 做构造器映射。另外 record 是 final 的,不能继承、也不能被继承,但可以实现接口。
什么情况下仍然要用 Lombok?
结论:受限于 JDK 版本、需要可变状态、或者框架强制要求 JavaBean 规范时,Lombok 仍是唯一选择。
三类硬约束:
- JPA 实体不能用 record。JPA 规范要求实体有 public/protected 无参构造器和非 final 字段,Hibernate 要能通过反射设值,record 两条都不满足。
- 需要 setter 的场景。record 没有 setter,要"改一个字段"只能
new一个新对象(Lombok 有@With,record 没有等价物)。 - JDK 版本卡住。还在 JDK 8 或 11 的项目,record 根本不存在,只能靠 Lombok。
另外提醒一个版本坑:Lombok 对 JDK 版本很敏感,JDK 21 需要 Lombok 1.18.30 及以上,升级 JDK 时忘升 Lombok 会直接编译失败报 java.lang.NoSuchFieldError: com.sun.tools.javac.tree.JCTree$JCImport。record 则没有这个问题,它是 JDK 自带的。
record 和 Lombok 能一起用吗?
结论:能,而且很实用。record 负责不可变语义,Lombok 负责补 Builder。
较新版本的 Lombok(1.18.20 起)已支持在 record 上使用 @Builder:
@Builder
public record UserDTO(Long id, String name, List<String> roles) {}
这样字段多的 DTO 就能用 UserDTO.builder().id(1L).name("a").build() 构造,可读性比一堆位置参数强得多。建议在 record 的紧凑构造器里对集合做 List.copyOf(roles),否则外部仍能改到这个 List,record 只保证"引用不可变",不保证"内容不可变"——这是浅不可变最常见的误解。
迁移到 record 时要注意哪些坑?
结论:三个高频问题——访问器命名、序列化机制、工具链假设。
第一,getName() 变 name(),任何反射、SpEL 表达式、模板引擎访问属性的代码都要检查。第二,record 的序列化走 JDK 专用路径,不能自定义 writeObject/readObject,反序列化直接调规范构造器,所以构造器里的校验在反序列化时同样生效(这其实是好事,但校验失败会抛异常)。第三,一些老库默认"有 setter 就能填值",遇到 record 会静默失败,升级前先在测试里跑一遍。
收个尾:**新项目、JDK 16+、数据不可变——record;存量项目、可变对象、JPA 实体——Lombok;字段多又想不可变——record + @Builder。** 判断标准不是"哪个更时髦",而是"这个类需不需要被修改、被继承、被框架反射赋值"。
转载请注明出处,版权归原作者所有。