1. 简介

Java 16 引入的 records 提供了一种简洁的不可变数据建模方式。它们自动生成构造器、访问器、equals()hashCode()toString() 方法,减少了样板代码并提升了可读性。

尽管优势明显,但 records 也存在显著限制:

  • 所有字段必须在记录头部声明
  • 禁止使用 setter 方法
  • 隐式 final 特性限制了扩展性

这些约束使得 records 在需要灵活对象创建的场景(如可选参数或实例修改)中显得力不从心。RecordBuilder 库 提供了优雅的解决方案,通过构建器模式在保持不可变性的同时增强灵活性。

2. 快速开始

使用 RecordBuilder 首先需要添加注解处理器依赖。Maven 配置如下:

<dependency>
    <groupId>io.soabase.record-builder</groupId>
    <artifactId>record-builder-core</artifactId>
    <version>47</version>
</dependency>

配置完成后,在 record 上添加 @RecordBuilder 注解,并可选实现生成的 With 接口以启用内联构建器和流畅的 withX() 方法。

3. 为什么选择 RecordBuilder?

纯 records 的优势显而易见——用最简语法实现不可变数据结构:

public record Person(String name, int age) {}

但全参数构造器和缺乏 setter 使其在需要灵活性时显得僵硬。手动编写构建器 会重新引入 records 旨在消除的样板代码。

RecordBuilder 巧妙地解决了这个矛盾: ✅ 单个注解即可获得流畅、安全、可读的构建和修改方式 ✅ 支持分阶段构建器、withX() 方法、自定义钩子等 ✅ 完全保持不可变性

例如:

@RecordBuilder
public record Person(String name, int age) implements PersonBuilder.With {}

RecordBuilder 会自动生成完整的构建器方法、withX() 访问器和静态工厂工具,全部遵循不可变性最佳实践。

4. 使用生成的构建器

通过 RecordBuilderDemo 类演示构建器如何提升开发效率:

首先用标准方式创建初始记录:

Person p1 = new Person("foo", 123);
assertEquals("foo", p1.name());
assertEquals(123, p1.age());

通过生成的 withName()withAge() 方法更新字段:

Person p2 = p1.withName("bar");
assertEquals("bar", p2.name());
assertEquals(123, p2.age());

Person p3 = p2.withAge(456);
assertEquals("bar", p3.name());
assertEquals(456, p3.age());

每次转换都保持不可变性,仅修改目标字段。更强大的功能是流畅构建器语法

Person p4 = p3.with()
  .age(101)
  .name("baz")
  .build();
assertEquals("baz", p4.name());
assertEquals(101, p4.age());

当需要修改多个字段时,这种风格显著提升代码清晰度,避免构造器链式调用,使每个转换意图明确。接下来我们将看到构建器还支持基于 lambda 的内联修改。

5. 高级特性

RecordBuilder 的亮点之一是支持基于 lambda 的内联修改

Person p5 = p4.with(p -> p.age(200).name("whatever"));
assertEquals("whatever", p5.name());
assertEquals(200, p5.age());

还能在构建器上下文中应用条件逻辑:

Person p6 = p5.with(p -> {
    if (p.age() > 13) {
        p.name("Teen " + p.name());
    } else {
        p.name("whatever");
    }
});
assertEquals("Teen whatever", p6.name());
assertEquals(200, p6.age());

需要更多控制时,可使用静态构建器工厂(尤其在记录实例外部操作时):

Person p7 = PersonBuilder.from(p6)
  .with(p -> p.age(300).name("Manual Copy"));
assertEquals("Manual Copy", p7.name());
assertEquals(300, p7.age());

或直接更新单个字段:

Person p8 = PersonBuilder.from(p6)
  .withName("boop");
assertEquals("boop", p8.name());
assertEquals(200, p8.age());

这种静态形式在处理独立构建器、外部数据转换或测试工具时特别有用,实现了构建逻辑与业务逻辑的清晰分离。

6. 自定义与选项

RecordBuilder 不仅生成基础构建器,还提供强大的自定义功能

  • 分阶段构建器:编译时强制必填字段设置,防止运行时错误
  • 测试框架集成:轻松创建测试固件或注入部分构建对象
  • 架构适配:在不牺牲不可变性的前提下,将构建器逻辑与应用架构对齐

这些特性使 RecordBuilder 成为生产环境中灵活可靠的工具。

7. 对比:RecordBuilder vs 手动构建器

对于字段较少的 record,手动编写构建器看似可行。但随着记录结构演进,手动构建器很快会成为维护噩梦

  • 每次记录变更都需要同步更新构建器
  • 新增字段、构造器逻辑、withX() 方法和 build() 逻辑都需要手动调整

RecordBuilder 通过注解处理彻底消除这个负担: ✅ 编译时生成构建器,与记录结构完美同步 ✅ 记录头部的任何修改(增删/重排字段)自动处理 ✅ 消除常见错误源,提升长期可维护性

此外,RecordBuilder 提供的基于 lambda 的更新和静态克隆方法,手动实现需要大量工作。在开发效率、正确性和一致性方面,RecordBuilder 远超手动实现。

8. 结论

RecordBuilder 库让 Java records 的使用更便捷、更实用。它生成流畅且兼容不可变性的构建器,无需手动编写构造器或更新方法。

其核心优势在于平衡性

  • 灵活更新 + 保持不可变性
  • 表达力强 + 零样板代码

无论是构建 DTO、API 响应还是配置对象,它都能自然融入开发流程。几乎零配置的情况下,RecordBuilder 成为构建简洁、可扩展 Java 代码的强大工具。

本文完整源码可在 GitHub 获取


原始标题:A Practical Guide to RecordBuilder in Java | Baeldung