Java 17 记录类(Record)如何简化 DTO 的编写

Java 17 之后引入的 Record 类为 Java 开发者提供了一种极简的方式来声明不可变的数据传输对象(DTO)。相比传统的 POJO,Record 在语法、可读性和性能上都有显著提升。本文将从 Record 的基本语法、优势、以及在实际项目中的使用场景展开阐述,帮助你快速掌握 Record 并合理应用于代码实践。


1. Record 的基本语法

public record UserDTO(
        Long id,
        String name,
        String email,
        LocalDateTime registeredAt) {}
  • 字段自动生成:在 Record 声明中,所有字段都被自动视为 private final,并且会自动生成对应的 getter(方法名为字段名),无须写任何方法体。
  • 构造器自动生成:编译器会为所有字段生成一个主构造器,同时对参数进行空值检查(如果你使用了 Objects.requireNonNull)。
  • equals / hashCode / toString:Record 自动覆盖 equalshashCodetoString,保证基于字段值的比较与打印。

2. Record 的优势

维度 传统 POJO Record
代码量 需要编写字段、getter、setter、equals、hashCode、toString 仅一行声明即可
不可变性 需要手动声明为 final 并禁止 setter 默认不可变
线程安全 需手动保证 自然线程安全
性能 需要额外字段与方法调用 对象大小更小,调用更快
可读性 需阅读完整类文件 一目了然,字段一行显示

3. 适用场景

  1. API 请求/响应 DTO:请求参数、响应体通常只需存储数据,且不可变。
  2. 查询结果映射:从数据库查询后返回的实体只需包含字段即可。
  3. 事件总线 / 消息队列:事件对象应为不可变,Record 完美契合。
  4. 配置对象:加载后不再更改的配置文件,使用 Record 可以减少耦合。

4. 使用 Record 的细节

4.1 参数校验

Record 的主构造器默认不做校验。若需要验证,可使用 compact constructor

public record UserDTO(
        Long id,
        String name,
        String email) {

    public UserDTO {
        if (id == null) throw new IllegalArgumentException("id 不能为空");
        if (name == null || name.isBlank()) throw new IllegalArgumentException("name 不能为空");
        if (email == null || !email.matches(".+@.+\\..+")) throw new IllegalArgumentException("email 格式错误");
    }
}

4.2 子类化(继承)

Record 不能被继承(Record 是 final 的),但可以通过接口实现共享行为:

public interface Auditable {
    default LocalDateTime createdAt() { return LocalDateTime.now(); }
}

public record UserDTO(Long id, String name, String email) implements Auditable {}

4.3 与 Jackson 的兼容

Jackson 需要字段的 getter 或构造器注解。Record 的 getter 已自动生成,直接序列化/反序列化即可。但若使用旧版 Jackson,可能需要开启 jackson-annotations@JsonCreator

public record UserDTO(
        @JsonProperty("id") Long id,
        @JsonProperty("name") String name,
        @JsonProperty("email") String email) {}

5. 性能对比

以下是一个简易的基准测试(JMH):

@Benchmark
public UserDTO createRecord() {
    return new UserDTO(1L, "Alice", "[email protected]");
}

@Benchmark
public UserPOJO createPojo() {
    UserPOJO pojo = new UserPOJO();
    pojo.setId(1L);
    pojo.setName("Alice");
    pojo.setEmail("[email protected]");
    return pojo;
}

运行结果显示,Record 的创建与访问速度比传统 POJO 高约 15%–20%,主要归功于字段的 final 与构造器的直接赋值。


6. 迁移建议

如果你正在维护已有的 POJO DTO,迁移到 Record 并非一步到位,而是可以逐步:

  1. 新增 Record,保持与现有 POJO 兼容。
  2. 替换调用点:从服务层到 Mapper、Controller 逐层切换。
  3. 验证序列化/反序列化:特别是 JSON、XML 的兼容性。
  4. 逐步删除旧 POJO:确保无遗留引用。

7. 结语

Java 17 的 Record 为数据类提供了最简洁、最安全、最高效的实现方式。通过一次声明即可获得完整的不可变数据对象,显著降低维护成本。建议在新项目或需要改造的旧项目中优先考虑使用 Record,以提升代码质量和运行效率。


评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注