使用 Lombok 为 类 创建具有必需和可选属性的构建器

Using Lombok to create builders for classes with required and optional attributes

正在搜索一个插件以避免样板代码来实现 Joshua Bloch's builder pattern I found the amazing Lombok Project,它使您能够通过这样的注释生成构建器:

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

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

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

结束链

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

Because construction is split across multiple calls, a JavaBean may be in an inconsistent state partway through its construction.

假设在上面的示例中,前两个属性 name 和 address 是构造 Person 实例所必需的,Lombok 实现构建器模式的方式使开发人员能够 split/shorten 构造并执行Person 实例可能不一致的东西,例如:

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

Joshua Bloch's solution prefers a builder method with the mandatory attributes as parameters, so that there is no possibility that the construction is split across multiple calls, like illustrated in Item 2: Consider a builder when faced with many constructor parameters.

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

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

对我有用的描述如下:

这有点样板,可以避免。在下面的 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();
    }
}

根据 @Builder docs,此注释可以与 @NonNull 一起使用。如果标记为 @NonNull 的字段是 null,您将得到一个 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() 来创建构建器,因为构建器 class 仍然可以访问。

此外,在扩展中可以使用:

@Builder.Default <modifier><instanceVariable>=<default-value>

赞:@Builder.Default private String myVariable = ""

请阅读:@Builder default properties

避免编译器错误询问在某些情况下不会设置的必需属性,例如持久性、索引等(update/deleteBy/findBy 来自 id 或简化的属性集,没有完整的对象图)。

这是一个优雅的解决方案,不按照建议覆盖 .build()setters