1. 概述

在本教程中,我们将使用 FreeBuilder 库来自动生成 Java 中的 Builder 类。

FreeBuilder 是一个开源库,它利用 Java 的注解处理机制,自动生成 Builder 模式所需的代码,从而大幅减少样板代码(boilerplate code)的编写。


2. Builder 设计模式简介

Builder 模式是面向对象语言中最常用的创建型设计模式之一。它通过封装复杂对象的构建过程,提供一个流畅的 API 来创建对象,从而帮助我们保持领域层的简洁。

尽管 Builder 模式非常实用,但在 Java 中手动实现 Builder 类往往需要大量样板代码。即使是简单的值对象,也需要写很多重复代码。


3. Java 中的传统 Builder 实现

我们先手动实现一个 Employee 类的 Builder:

public class Employee {

    private final String name;
    private final int age;
    private final String department;

    private Employee(String name, int age, String department) {
        this.name = name;
        this.age = age;
        this.department = department;
    }

    public static class Builder {

        private String name;
        private int age;
        private String department;

        public Builder setName(String name) {
            this.name = name;
            return this;
        }

        public Builder setAge(int age) {
            this.age = age;
            return this;
        }

        public Builder setDepartment(String department) {
            this.department = department;
            return this;
        }

        public Employee build() {
            return new Employee(name, age, department);
        }
    }
}

使用方式如下:

Employee employee = new Employee.Builder()
  .setName("baeldung")
  .setAge(12)
  .setDepartment("Builder Pattern")
  .build();

✅ 可以看到,手动实现 Builder 类需要大量重复代码,尤其是当字段变多时。


4. Maven 依赖配置

要使用 FreeBuilder,只需在 pom.xml 中添加以下依赖:

<dependency>
    <groupId>org.inferred</groupId>
    <artifactId>freebuilder</artifactId>
    <version>2.4.1</version>
</dependency>

⚠️ 注意:使用 FreeBuilder 时,IDE 可能会报错找不到生成的类(如 Employee_Builder),这是因为 FreeBuilder 是在编译阶段生成代码的。

解决方法:

  • IntelliJ 用户:开启注解处理(Annotation Processing),路径:Settings → Build → Compiler → Annotation Processors
  • Eclipse 用户:启用 APT(Annotation Processing Tooling)
  • 同时,记得将生成目录标记为 Generated Sources Root

5. 使用 @FreeBuilder 注解

5.1. 自动生成 Builder

使用 FreeBuilder 后,Employee 类将变成一个接口:

@FreeBuilder
public interface Employee {

    String name();
    int age();
    String department();

    class Builder extends Employee_Builder {
    }
}

使用方式如下:

Employee employee = new Employee.Builder()
  .name("baeldung")
  .age(10)
  .department("Builder Pattern")
  .build();

⚠️ 注意两点:

  1. 所有字段必须设置,否则运行时会抛出 IllegalStateException
  2. 方法名不符合 JavaBean 命名规范(如没有 setXxx

5.2. 支持 JavaBean 命名规范

为了让生成的方法名符合 JavaBean 规范,只需在接口中使用 getXxx 格式命名:

@FreeBuilder
public interface Employee {

    String getName();
    int getAge();
    String getDepartment();

    class Builder extends Employee_Builder {
    }
}

此时生成的 Builder 方法就是标准的 setXxx

Employee employee = new Employee.Builder()
  .setName("baeldung")
  .setAge(10)
  .setDepartment("Builder Pattern")
  .build();

5.3. Mapper 方法(函数式支持)

FreeBuilder 还为每个字段生成 mapXxx 方法,接受 UnaryOperator 参数,用于变换字段值:

@FreeBuilder
public interface Employee {
    Optional<Double> getSalaryInUSD();
}

使用示例:

double salaryInEuros = 5000;
Employee employee = new Employee.Builder()
  .setName("baeldung")
  .setAge(10)
  .mapSalaryInUSD(sal -> salaryInEuros * 1.1) // 模拟欧元转美元
  .build();

6. 默认值与校验逻辑

6.1. 设置默认值

如果某些字段可以有默认值,可以直接在 Builder 构造函数中设置:

class Builder extends Employee_Builder {
    public Builder() {
        setDepartment("Default Department");
    }
}

这样即使不调用 setDepartment(),也会使用默认值。


6.2. 添加字段校验

FreeBuilder 允许我们在 setXxx 方法中添加校验逻辑:

@Override
public Builder setEmail(String email) {
    if (email == null || !email.contains("@")) {
        throw new IllegalArgumentException("Invalid email");
    }
    return super.setEmail(email);
}

这样就能在构建对象时自动校验字段合法性。


7. 可选字段处理

7.1. 使用 Optional 字段

对于可选字段,推荐使用 Optional<T>

@FreeBuilder
public interface Employee {
    Optional<String> getDateOfJoining();
}

使用时可以不设置该字段:

Employee employee = new Employee.Builder()
  .setName("baeldung")
  .setAge(10)
  .build();

此时 getDateOfJoining() 返回 Optional.empty()


7.2. 使用 @Nullable 注解

如果出于兼容性考虑,也可以使用 @Nullable

@Nullable
String getCurrentProject();

⚠️ 但建议优先使用 Optional,因为它是 Java 8+ 推荐的处理空值的方式。


8. 集合与 Map 支持

FreeBuilder 对集合和 Map 有内置支持:

@FreeBuilder
public interface Employee {
    List<Long> getAccessTokens();
    Map<String, Long> getAssetsSerialIdMapping();
}

FreeBuilder 会自动生成 addXxxputXxx 方法:

Employee employee = new Employee.Builder()
  .setName("baeldung")
  .addAccessTokens(1221819L)
  .addAccessTokens(1223441L, 134567L)
  .putAssetsSerialIdMapping("Laptop", 12345L)
  .build();

这些方法返回的集合是 不可变的(unmodifiable),防止外部修改。


9. 嵌套 Builder 支持

FreeBuilder 支持嵌套 Builder,适用于复杂对象结构:

@FreeBuilder
public interface Address {
    String getCity();
    class Builder extends Address_Builder {}
}

然后在 Employee 中引用它:

@FreeBuilder
public interface Employee {
    Address getAddress();
}

使用方式如下:

Address.Builder addressBuilder = new Address.Builder().setCity("Beijing");

Employee employee = new Employee.Builder()
  .setName("baeldung")
  .setAddress(addressBuilder)
  .mutateAddress(a -> a.setCity("Shanghai"))
  .build();

mutateAddress() 方法可以修改已设置的嵌套对象。


10. 构建部分对象(Partial Build)

在测试时,我们可能不想强制设置所有字段,这时可以使用 buildPartial()

Employee employee = new Employee.Builder()
  .setName("baeldung")
  .setAge(10)
  .buildPartial();

即使没有设置所有字段,也能成功构建对象,适合用于单元测试。


11. 自定义 toString() 方法

FreeBuilder 支持自定义 toString() 方法,只需将类定义为抽象类即可:

@FreeBuilder
public abstract class Employee {

    public abstract String getName();
    public abstract int getAge();

    @Override
    public String toString() {
        return getName() + " (" + getAge() + " years old)";
    }
}

这样就可以自定义输出格式,而不会影响 Builder 的生成逻辑。


12. 与其他 Builder 库对比

FreeBuilder 与 Lombok、Immutables 等库类似,都能生成 Builder 类。但 FreeBuilder 有几个显著优势:

✅ 支持 Mapper 方法
✅ 支持嵌套 Buildable 类型
✅ 支持 Partial Build
✅ 更加灵活的定制能力


13. 总结

本文介绍了如何使用 FreeBuilder 自动生成 Builder 类,从而减少样板代码。我们展示了如何设置默认值、添加字段校验、处理可选字段、集合支持、嵌套 Builder 以及构建部分对象等特性。

FreeBuilder 是一个功能强大且灵活的工具,适合用于构建不可变对象或复杂对象结构的项目。

所有代码示例可在 GitHub 上找到。


原始标题:Automatic Generation of the Builder Pattern with FreeBuilder

« 上一篇: Java 中的 SASL 简介
» 下一篇: Java Hello World 示例