Lombok @Builder 的必需参数

Required arguments with a Lombok @Builder

如果我将 @Builder 添加到 class。生成器方法已创建。

Person.builder().name("john").surname("Smith").build();

我有一个需要特定字段的要求。在这种情况下,名称字段是必需的,但姓氏不是。理想情况下,我想这样声明。

Person.builder("john").surname("Smith").build()

我不知道该怎么做。我尝试将 @Builder 添加到构造函数中,但没有成功。

@Builder
public Person(String name) {
    this.name = name;
}

您可以使用 Lombok 注释配置轻松完成

import lombok.Builder;
import lombok.ToString;

@Builder(builderMethodName = "hiddenBuilder")
@ToString
public class Person {

    private String name;
    private String surname;

    public static PersonBuilder builder(String name) {
        return hiddenBuilder().name(name);
    }
}

然后就这样使用

Person p = Person.builder("Name").surname("Surname").build();
System.out.println(p);

当然@ToString这里是可选的

这是另一种方法:

@Builder()
@Getter
@ToString
public class Person {

    private final String name;
    private final String surname;

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

    public static void main(String[] args) {
        Person p = Person.builder("John Doe")
                .surname("Bill")
                .build();
    }
}

我建议不要使用这种方法,因为您很难将它始终如一地应用于其他对象。相反,您可以只使用 @lombok.NonNull 注释标记字段,Lombok 将在构造函数和设置器中为您生成空检查,因此如果未设置这些字段,Builder.build() 将失败。

使用构建器模式可以让您非常清楚地识别要为哪些字段设置哪些值。在您的示例中,名称字段已经丢失,如果您正在构建具有多个必填字段的对象,所有其他必填字段将进一步丢失。考虑下面的例子,你能通过阅读代码分辨出哪个字段是哪个字段吗?

Person.builder("John", "Michael", 16, 1987) // which is name, which is surname? what is 16?
    .year(1982) // if this is year of birth, then what is 1987 above?
    .build()

这是我对问题的解决方案

import lombok.Builder;
import lombok.Data;
import lombok.NonNull;

@Data
@Builder(builderMethodName = "privateBuilder")
public class Person {
    @NonNull
    private String name;
    @NonNull
    private String surname;
    private int age;//optional

public static Url safeBuilder() {
    return new Builder();
}

interface Url {
    Surname name(String name);
}

interface Surname {
    Build surname(String surname);
}

interface Build {
    Build age(int age);
    Person build();
}

public static class Builder implements Url, Surname, Build {
    PersonBuilder pb = Person.privateBuilder();

    @Override
    public Surname name(String name) {
        pb.name(name);
        return this;
    }

    @Override
    public Build surname(String surname) {
        pb.surname(surname);
        return this;

    }

    @Override
    public Build age(int age) {
        pb.age(age);
        return this;
    }

    @Override
    public Person build() {
        return pb.build();
    }
    }
}

受此博客启发 post:

https://blog.jayway.com/2012/02/07/builder-pattern-with-a-twist/

最简单的解决方案是为所有必填值添加一个 @lombok.NonNull。如果未设置必填字段,Builder 将无法构建对象。

这是一个 JUnit 测试,用于演示 final@NonNull 的所有组合的行为:

import static org.junit.Assert.fail;

import org.junit.Test;

import lombok.Builder;
import lombok.ToString;

public class BuilderTests {
    @Test
    public void allGiven() {
        System.err.println(Foo.builder()
            .nonFinalNull("has_value")
            .nonFinalNonNull("has_value")
            .finalNull("has_value")
            .finalNonNull("has_value")
            .build());
    }

    @Test
    public void noneGiven() {
        try {
            System.err.println(Foo.builder()
                .build()
                .toString());
            fail();
        } catch (NullPointerException e) {
            // expected
        }
    }

    @Test
    public void nonFinalNullOmitted() {
        System.err.println(Foo.builder()
            .nonFinalNonNull("has_value")
            .finalNull("has_value")
            .finalNonNull("has_value")
            .build());
    }

    @Test
    public void nonFinalNonNullOmitted() {
        try {
            System.err.println(Foo.builder()
                .nonFinalNull("has_value")
                .finalNull("has_value")
                .finalNonNull("has_value")
                .build());
            fail();
        } catch (NullPointerException e) {
            // expected
        }
    }

    @Test
    public void finalNullOmitted() {
        System.err.println(Foo.builder()
            .nonFinalNull("has_value")
            .nonFinalNonNull("has_value")
            .finalNonNull("has_value")
            .build());
    }

    @Test
    public void finalNonNullOmitted() {
        try {
            System.err.println(Foo.builder()
                .nonFinalNull("has_value")
                .nonFinalNonNull("has_value")
                .finalNull("has_value")
                .build());
            fail();
        } catch (NullPointerException e) {
            // expected
        }
    }

    @Builder
    @ToString
    private static class Foo {
        private String nonFinalNull;

        @lombok.NonNull
        private String nonFinalNonNull;

        private final String finalNull;

        @lombok.NonNull
        private final String finalNonNull;
    }
}

让凯文·戴的 更进一步:

@Builder
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE) // If immutability is desired
@ToString
public class Person {
    @NonNull // Presumably name cannot be null since its required by the builder
    private final String name;
    private final String surname;

    private static PersonBuilder builder() {
        return new PersonBuilder();
    }

    public static PersonBuilder builder(String name){
        return builder().name(name);
    }

}

这并不理想,但它提供了编译时强制执行,并且此 class 的调用者将只有一个构建器方法可以使用。

如果你需要这个功能,你可以自己定制构建器class,你仍然可以添加@Builder注解。

@Builder
public class Person {

    public static class PersonBuilder {
        private String name;

        private PersonBuilder() {
        }

        public PersonBuilder(final String name) {
            this.name = name;
        }
    }

    private static PersonBuilder builder() {
        return null; // or we can throw exception.
    }

    public static PersonBuilder builder(final String name) {
        return new PersonBuilder(clientId);
    }
}

Userclass为例,id字段必填:

@AllArgsConstructor(access = AccessLevel.PRIVATE) // required, see 
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Builder
@Getter
public class User {
    private String id;
    private String name;
    private int age;

    public static UserBuilder builder(final String id) {
        return new UserBuilder().id(id);
    }
}

您只能User user = User.builder("id-123").name("Tom").build; 这样的构建器初始化一个User 实例。使用 private no args 构造函数,您无法 User user = new User();User user = new User("id-123"); 因此您始终需要传递所需的参数 id。请注意初始化的实例是不可变的。

结合@Pawel 的回答和 Max 的评论 ...

import lombok.Builder;
import lombok.ToString;

@Builder
public class Person {

  private String name;
  private String surname;

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

最佳实践:

import lombok.Builder;
import lombok.NonNull;

@Builder(builderMethodName = "privateBuilder")
public class Person {
    @NonNull
    private String name;
    private String surname;

    public static class PersonNameBuilder {
        public PersonBuilder name(String name) {
            return Person.privateBuilder().name(status);
        }
    }

    public static PersonNameBuilder builder(String name) {
        return new PersonNameBuilder();
    }

    private static PersonBuilder privateBuilder(){
        return new PersonBuilder();
    }
}

用法:

PersonNameBuilder nameBuilder = Person.builder();
PersonBuilder builder = nameBuilder.name("John");
Person p1 = builder.surname("Smith").build();

// Or
Person p2 = Person.builder().name("John").surname("Smith").build();

尽管我很想拥有编译时验证功能,但库的作者已经明确表示 the feature probably won't be added.

所以我对此的看法是,拥有这样的东西。

@Builder
public class Person {
  String name;
  Integer age;
  Optional optional;

  @Builder
  public class Optional {
    String surname;
    String companyName;
    String spouseName;
}

}

而且你可以像这样使用它

 Person p = Person.builder()
            .age(40)
            .name("David")
            .optional(Person.Optional.builder()
                    .surname("Lee")
                    .companyName("Super Company")
                    .spouseName("Emma")
                    .build())
            .build();

不,没有验证。 但从图书馆用户的角度来看, 很清楚什么是必需的,什么不是,并且能够在不查看文档的情况下构建对象实例。

这是 Pawel 响应的灵感,带有一个隐藏生成的构建器:

import lombok.Builder;
import lombok.ToString;

@Builder(builderMethodName = "")
@ToString
public class Person {

    private String name;
    private String surname;

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

为了使lombok的构建器实现隐含的限制和风险尽可能明显,从而减少错误滥用的可能性,我提出以下解决方案:

import lombok.Builder;

@Builder(builderClassName = "UnsafeBuilder")
public class Person {
    private String name;
    private String surname;

    public static UnsafeBuilder builder(String name) {
        return new UnsafeBuilder().name(name);
    }
}

按预期使用此解决方案非常简单:

Person thePerson = Person.builder("the_name").surname("the_surname").build();

以非预期方式使用生成器的唯一方法使风险显而易见,而且很可能不会被错误选择:

final Person unsafePerson = new Person.UnsafeBuilder().surname("the_surname").build();

class 名称当然可以更激进地选择 - 例如 NeverCallThisConstructor.