首页 > 解决方案 > 使用 Lombok 为具有必需和可选属性的类创建构建器

问题描述

搜索一个插件以避免样板代码来实现Joshua Bloch构建器模式,我发现了令人惊叹的Lombok 项目,它使您能够通过如下注释生成构建器:

@Builder
public class Person {
    private String name;
    private String address;
    private String secondAddress;
}

PersonBuilder.builder().name("yourName").address("your Address").build();

如您所见,没有样板代码,您可以Person通过调用提供的静态builder()方法轻松创建一个实例,链接 setter-like-calls 就像它使用 JavaBeans-Pattern 一样,并以调用结束链build()

与构建器模式相比,JavaBeans-Pattern 的缺点之一是(来自Effective Java):

因为构造被拆分为多个调用,所以 JavaBean 在构造过程中可能处于不一致的状态

假设在上面的示例中,前两个属性 name 和 address 对于构造 Person 的实例是必需的,Lombok 实现构建器模式的方式使开发人员能够拆分/缩短构造并使用可能不一致的实例做一些事情Person, 像这样:

Person p = PersonBuilder.builder().name("yourName").build();
...
System.out.println(p.getAddress());
...
p.setAddress("your address");

Joshua Bloch的解决方案更喜欢将强制属性作为参数的构建器方法,这样就不可能将构造拆分为多个调用,如第 2 条所示:在面对许多构造函数参数时考虑构建器

我的问题是: 是否有任何方便的方法,例如 @Builder 的注释参数或属性级别的 Springs @Required 或 @Mandatory 之类的东西,以强制 Lombok 避免提供无参数构建器构造函数并为构造函数提供强制参数,如 Joshua Bloch提议?

我尝试了@Builder 文档中的许多选项,但找不到理想的解决方案。

对我有用的描述如下:

这是一个样板,可以避免。请参阅我在 Joshua Bloch 的示例中应用的解决方案。

/**
 * Uncle Bobs builder example for constructors with many required & optional parameters,
 * realized by lombok.
 * 
 */
@AllArgsConstructor(access=AccessLevel.PRIVATE) // Let lombok generate private c-tor with all parameters, as needed by @Builder.
@Builder(
        builderClassName="Builder", // Cosmetic. Without this option, the builder class would have the name NutritionFactsBuilder.
        toBuilder=true // Enabling creation of a builder instance based on a main class instance: NutritionFacts. 
)
public class NutritionFacts {

    // Required parameters
    private int servingSize;
    private int servings;

    // Optional parameters
    private int calories;
    private int fat;
    private int sodium;
    private int carbohydrate;

    /**
     * A builder method demanding required parameters.
     */
    public static Builder builder(int servingSize, int servings) {
        return new NutritionFacts(servingSize, servings).toBuilder();
    }

    /**
     * eclipse-created C-tor with required parameters.
     * Can be public for instantiating, but doesn't have to.
     */
    public NutritionFacts(int servingSize, int servings) {
        super();
        this.servingSize = servingSize;
        this.servings = servings;
    }

    public static void main(String[] args) {
        NutritionFacts cocaCola = NutritionFacts.builder(240, 8)
                                                .calories(100)
                                                .sodium(35)
                                                .carbohydrate(27)
                                                .build();
    }
}

标签: javabuilderlombok

解决方案


根据@Builder文档,此注释可以与@NonNull. 如果标记的字段@NonNullnull您将NullPointerException阻止创建无效对象:

@Builder
static class Person {
  @NonNull
  private final String name;
  @NonNull
  private final Integer age;
}

public static void main(String[] args) {
  Person.builder()
        .name("Fred")
        .build(); // java.lang.NullPointerException: age is marked @NonNull but is null
}

要更进一步,您可以builder自己定义方法。如果该方法存在,Lombok 将不会生成它,您现在可以强制参数编译时间。

@Builder
static class Person {
  @NonNull
  private final String name;
  @NonNull
  private final Integer age;

  public static PersonBuilder builder(String name, Integer age) {
    return new PersonBuilder().name(name).age(age);
  }
}

public static void main(String[] args) {
  Person.builder("Fred", 11)
        .build();
}

然而,仍然可以通过编写来创建构建器,new Person.PersonBuilder()因为构建器类仍然是可访问的。


推荐阅读