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


原始标题:Using Lombok's @Builder Annotation | Baeldung