Lombok @Builder的必需参数


73

如果我将@Builder添加到类中。创建了builder方法。

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

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

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

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

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

Lombok在GitHub上公开的问题对此github.com/rzwitserloot/lombok/issues/1043
lennykey

Answers:


80

您可以使用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这里是可选的。


2
如果您能多解释一点会更好。
2015年

36
这个答案对我来说没有意义的是hiddenBuilder()没有被隐藏……
Kevin Day

4
@AkshatAgarwal是什么隐藏了生成器方法?我确定99.99%的builderMethodName会更改方法的名称-不会将方法更改为隐藏。因此,我仍然看不到有任何方法可以达到具有必填字段的预期结果。
凯文·戴

7
我只想告诉龙目岛,让建造者私有:@Builder(builderMethodName = "hiddenBuilder", access = AccessLevel.PRIVATE)
Linus

2
@Linus似乎添加AccessLevel.PRIVATE也会使所有构建器的方法也变为私有,并且认为它非常无用。我错了吗
迪恩·古维兹

48

我建议您不要采用这种方法,因为您将难以始终如一地将其应用于其他对象。相反,您可以仅使用@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()

30
运行时错误与编译时错误。始终支持编译时错误!
JAX

6
@jax他们不检查同一件事。要求设置字段不会检查空值。无论您是否需要该字段,检查null都是运行时错误(在纯Java中)。
Anton Koscejev '16

1
嗯,我明白您的意思了,但是您的方法也允许空值。最好是提前签订对象合同,而不是让程序员猜测。看看《 Effective Java 2nd edition》这本书中的构建器模式。
jax

1
@NotNull将在运行时评估!
jax

5
您也可以使用前面的答案构造空字段,例如Person.builder(null).lastName(“ John”)。build();。因此无论如何您仍然需要运行时检查。
Lakatos Gyula

19

将Kevin Day的答案进一步:

@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);
    }

}

这不是理想的方法,但是它提供了编译时间强制,并且此类的调用者将只使用一种构建器方法。


1
很好,但是仍然有可能通过调用覆盖该值(错误),.name()因此我们将拥有:Person.builder("john").surname("smith").name("mark").build();当然,最终我们将变得mark不想要john。我知道这是一个牵强的案例,但可能的,完美的解决方案可以避免这种情况。您认为有可能对此进行辩护吗?(我现在正在处理)
user3529850 18-10-27

1
@ user3529850如果您有必填字段,并且由于某种原因在构建器的上下文中存在不可变性问题,那么最好只用旧的方式编写高度自定义的构建器,而无需Lombok。请注意,在我给出的示例中,Person是不可变的。您只能将名称设置一次。您正在执行的操作是在PersonBuilder上多次设置名称,这并非一成不变。
吉尔伯特·阿里纳斯·匕首'18

@ user3529850即使使用经典的生成器方法,您也可以始终在构造函数中传递null ...
Amit Goldstein

15

这是另一种方法:

@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();
    }
}

我更喜欢您的方法,因为它会使builder方法变得简明而自然,从而使该方法过载。
Jezor

6
这种方法的问题在于builder()仍然可见,因此它并没有真正使参数成为必需。不管怎样,是的,我们还需要使用@NonNull批注-但这是运行时检查-如果要尝试制作一个超直观的对象,绝对不够好。可惜的是,在lombok中没有一种方法可以裁剪这种东西-即使我们可以通过某种方式将builder()方法设置为私有,也可以创建自己的重载公共builder(...)方法具有必需的参数。
凯文·戴

7

这是我解决问题的方法

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();
    }
    }
}

受此博客文章启发:

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


19
这就是我想要由龙目岛生成的东西。
okutane '17


5

最简单的解决方案是@lombok.NonNull在所有必填值中添加a 。未设置必填字段时,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;
    }
}

3

User班级为例,id必填字段:

@AllArgsConstructor(access = AccessLevel.PRIVATE) // required, see /programming/51122400/why-is-lombok-builder-not-compatible-with-this-constructor
@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 = User.builder("id-123").name("Tom").build;。使用private no args构造函数,您将无法User user = new User();User user = new User("id-123");因此始终需要传递必需的参数id。请注意,初始化的实例是不可变的。


1

结合@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);
  }
}

请注意,不幸的是,这不适用于@SuperBuilder,因为生成的生成器类也是抽象的。
Marv

0

如果需要此功能,则可以自己定制构建器类,并且仍然可以添加@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);
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.