Record类在DTO场景的核心价值是:以一行声明自动实现不可变数据载体所需的所有模板代码,包括final字段、getter、equals/hashCode/toString,语义清晰且字节码精简。

Record 类在 DTO 场景下的核心价值是什么
Java 14 引入的 record 是为不可变数据载体量身定制的语法糖。它天然适合 DTO(Data Transfer Object)——这类对象通常只承载字段、提供 getter、支持 equals/hashCode/toString,不包含业务逻辑。用 record 替代传统 class 定义 DTO,能直接省掉构造器、getter、equals()、hashCode()、toString() 的手写代码,且编译后字节码更精简。
关键判断:如果你的 DTO 满足「只读 + 字段即状态 + 无行为」,record 就是更优解;一旦需要 setter、字段校验逻辑、延迟计算或继承,就得退回到普通 class。
如何正确定义一个带验证的 Record DTO
record 本身禁止字段赋值,但允许在构造器中做参数校验。不能写 setter,但可以在 compact constructor 中抛异常或规范化输入。
常见错误现象:直接在 record 声明里调用 Objects.requireNonNull() 或做 trim,结果编译失败 —— 因为 record 的隐式构造器不允许语句块外的逻辑。
- 必须使用 compact constructor(即与 record 同名、无返回值、无修饰符的构造器)来插入校验逻辑
- 校验失败应抛
IllegalArgumentException,而非自定义异常(避免破坏 record 的不可变契约) - 不要在 compact constructor 中修改参数引用(如
name = name.trim()),这会改变入参,且违反 record 设计意图;如需标准化,应新建局部变量并用于字段初始化
record UserDTO(String name, Integer age) {
public UserDTO {
if (name == null || name.trim().isEmpty()) {
throw new IllegalArgumentException("name cannot be blank");
}
if (age == null || age 150) {
throw new IllegalArgumentException("age must be between 0 and 150");
}
}
}
Record DTO 与 Jackson / Lombok 兼容性要注意什么
Spring Boot 2.6+ 和 Jackson 2.12+ 原生支持 record,但仍有几个易踩的坑:
使用场景:Web 层接收 JSON 请求体(@RequestBody)、响应序列化(@ResponseBody)时,默认能正常工作;但若 DTO 字段名与 JSON key 不一致,或需忽略 null 字段,配置方式和 class 略有不同。
-
@JsonProperty可加在 record 参数上(不是字段),例如record UserDTO(@JsonProperty("user_name") String name, ...) -
@JsonIgnore加在参数上可忽略该字段反序列化/序列化 - Lombok 的
@Data、@Builder与record冲突,不能混用;若需 Builder 模式,可用record+ 静态工厂方法,或改用Builderrecord(较重,一般不推荐) - Spring Validation(如
@NotBlank)需配合@Valid使用,且注解必须加在 record 参数上,而非 getter(因为没有 getter 方法)
什么时候不该用 Record 定义 DTO
不是所有“看起来像 DTO”的类都适合 record。容易被忽略的边界情况包括:
性能影响:record 所有字段默认 public final,反射访问开销略低于普通 class 的 getter(尤其在高频序列化场景),但这点差异通常可忽略;真正影响大的是语义误用。
- 字段需要运行时动态计算(如
getFullName())—— record 不允许定义非字段相关方法(除非是静态或私有辅助方法) - DTO 需要实现接口(如
Serializable以外的业务接口)—— record 只能 implements 接口,但无法 override 接口中的 default 方法来改变行为 - 字段类型含泛型通配符(如
List extends Number>)—— record 构造器参数签名不支持通配符,会导致编译失败 - 需要与旧版 JDK(
最常被低估的一点:record 的不可变性是语言级保证,一旦 DTO 在后续迭代中出现“这个字段其实应该能改”的需求,重构成本远高于从 class 开始设计。










