1. 概述
Lombok 的 @Builder
注解是一个非常实用的工具,它让我们无需手写样板代码就能轻松实现建造者模式(Builder Pattern)。这个注解可以加在类上,也可以加在方法上,灵活性很高。
本文将系统性地讲解 @Builder
的各种使用场景,涵盖常见用法和一些进阶技巧,帮你避免日常开发中的“踩坑”。
2. Maven 依赖
要使用 Lombok,首先需要引入对应的依赖。Maven 配置如下:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
✅ 建议使用最新稳定版本,可在 Maven Central 查询。
⚠️ 注意:Lombok 依赖通常设置为 provided
,因为它在编译期生效,运行时并不需要。
3. 在类上使用 @Builder
这是最常见、最直观的用法。我们只需要在类上加上 @Builder
,Lombok 就会自动生成一个完整的建造者类。
基础用法
@Getter
@Builder
public class Widget {
private final String name;
private final int id;
}
✅ 生成效果:Lombok 会自动创建 WidgetBuilder
类,并提供链式调用的 setter 方法。
使用示例:
Widget testWidget = Widget.builder()
.name("foo")
.id(1)
.build();
assertThat(testWidget.getName()).isEqualTo("foo");
assertThat(testWidget.getId()).isEqualTo(1);
简单粗暴,代码瞬间清爽。
复制对象:toBuilder = true
如果我们想基于已有对象创建“副本”或“微调版本”,可以开启 toBuilder
功能:
@Builder(toBuilder = true)
public class Widget {
private final String name;
private final int id;
}
这样 Lombok 会为类生成一个 toBuilder()
方法,返回一个预填充了当前对象属性的 builder。
Widget original = Widget.builder().name("foo").id(1).build();
Widget modified = original.toBuilder().id(2).build();
assertThat(modified.getName()).isEqualTo("foo");
assertThat(modified.getId()).isEqualTo(2);
✅ 典型应用场景:DTO 转换、对象克隆、配置微调。
强制必填字段:自定义 builder 方法
有时候我们希望某些字段在构建时必须传入,可以通过自定义 builder 方法实现。
@Builder(builderMethodName = "internalBuilder")
public class RequiredFieldAnnotation {
@NonNull
private String name;
private String description;
public static RequiredFieldAnnotationBuilder builder(String name) {
return internalBuilder().name(name);
}
}
这样外部只能通过 builder(String name)
创建 builder,确保 name
字段不会为空。
使用方式:
RequiredFieldAnnotation obj = RequiredFieldAnnotation.builder("NameField")
.description("Field Description")
.build();
⚠️ 注意:
@NonNull
可以让 Lombok 在生成代码时自动加入 null 检查。- 这种方式适用于有强约束的领域模型。
4. 在方法上使用 @Builder
有时候我们无法修改目标类(比如第三方类、final 类),但又想用 builder 构造它。这时可以在静态工厂方法上使用 @Builder
。
场景示例:为 final 类创建 builder
假设我们有一个不可变类:
@Value
final class ImmutableClient {
private int id;
private String name;
}
@Value
是 Lombok 提供的注解,会生成 final 类、全参构造、getter、equals/hashCode 等。
我们无法直接在 ImmutableClient
上加 @Builder
,但可以创建一个包装类:
class ClientBuilder {
@Builder(builderMethodName = "builder")
public static ImmutableClient newClient(int id, String name) {
return new ImmutableClient(id, name);
}
}
✅ 效果:Lombok 会为 newClient
方法生成一个 builder,返回 ImmutableClient
实例。
使用方式:
ImmutableClient client = ClientBuilder.builder()
.id(1)
.name("foo")
.build();
assertThat(client.getName()).isEqualTo("foo");
assertThat(client.getId()).isEqualTo(1);
✅ 优势:
- 不侵入原始类
- 灵活控制 builder 参数
- 适合封装复杂构造逻辑
5. 排除字段:控制 builder 的字段可见性
有时候我们不希望所有字段都暴露在 builder 中,比如某些字段是内部使用、有默认值或由系统生成。
方式一:通过自定义静态工厂方法
我们可以定义一个只包含部分参数的静态方法,并加上 @Builder
。
public class ClassWithExcludedFields {
private int id;
private String includedField;
private String excludedField;
// setter methods...
}
添加自定义 builder 方法:
@Builder(builderMethodName = "customBuilder")
public static ClassWithExcludedFields of(int id, String includedField) {
ClassWithExcludedFields obj = new ClassWithExcludedFields();
obj.setId(id);
obj.setIncludedField(includedField);
return obj;
}
测试验证:
@Test
public void whenUsingCustomBuilder_thenExcludeUnspecifiedFields() {
ClassWithExcludedFields obj = ClassWithExcludedFields.customBuilder()
.id(3)
.includedField("Included Field")
// ❌ 没有 excludedField() 方法
.build();
assertThat(obj.getId()).isEqualTo(3);
assertThat(obj.getIncludedField()).isEqualTo("Included Field");
}
✅ 优点:完全掌控哪些字段暴露,适合复杂构造逻辑。
方式二:使用 @Builder.Default(推荐)
从 Lombok 1.16.16 开始,支持 @Builder.Default
注解,用于指定字段的默认值,并自动从 builder 中排除。
@Builder
public class ClassWithExcludedFields {
private int id;
private String includedField;
@Builder.Default
private String excludedField = "Excluded Field using Default";
}
✅ 使用时无需设置该字段,builder 会自动使用默认值。
测试:
@Test
public void whenUsingBuilderDefaultAnnotation_thenExcludeField() {
ClassWithExcludedFields obj = ClassWithExcludedFields.builder()
.id(3)
.includedField("Included Field")
.build();
assertThat(obj.getId()).isEqualTo(3);
assertThat(obj.getIncludedField()).isEqualTo("Included Field");
assertThat(obj.getExcludedField()).isEqualTo("Excluded Field using Default");
}
✅ 优势:
- 代码简洁
- 语义清晰
- 易于维护
⚠️ 注意:@Builder.Default
必须和 @Builder
一起使用,且字段必须有初始值。
6. 总结
使用场景 | 推荐方式 |
---|---|
普通类构建 | @Builder 直接加在类上 |
对象复制 | @Builder(toBuilder = true) |
必填字段控制 | 自定义 builder 方法 |
第三方类封装 | @Builder 加在静态工厂方法上 |
字段排除 | @Builder.Default 或自定义工厂方法 |
✅ @Builder
是提升代码可读性和减少样板代码的利器,合理使用能让代码更简洁、更安全。
所有示例代码已上传至 GitHub:https://github.com/baeldung/lombok-tutorial