Required arguments with a lombok @Builder

霸气de小男生 提交于 2019-12-09 04:05:43

问题


If I add @Builder to a class. The builder method is created.

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

I have a requirement where a particular field is required. In this case, the name field is required but the surname is not. Ideally, I would like to declare it like so.

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

I can't work out how to do this. I have tried adding the @Builder to a constructor but it didn't work.

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

回答1:


You can do it easily with Lombok annotation configuration

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

And then use it like that

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

Of course @ToString is optional here.




回答2:


I would recommend against this approach, as you will struggle to apply it consistently on other objects. Instead, you can just mark fields with @lombok.NonNull annotation and Lombok will generate null checks for you in the constructor and setters, so that Builder.build() will fail, if those fields are not set.

Using builder pattern allows you to have very clear identification of which fields you're setting to which values. This is already lost for name field in your example, and it will further be lost for all other required fields, if you're building an object with multiple required fields. Consider the following example, can you tell which field is which just by reading code?

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



回答3:


Taking Kevin Day's answer a step further:

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

}

It's not ideal, but it provides compile time enforcement and callers of this class will have exactly one builder method to use.




回答4:


Here's another approach:

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



回答5:


This is my solution for the problem

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

inspired by this blog post:

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




回答6:


The simpliest solution is to add a @lombok.NonNull to all mandatory values. The Builder will fail to build the object when mandatory fields are not set.

Here's a JUnit test to demonstrate the behavior of all combinations of final and @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;
    }
}



回答7:


Combining the answer from @Pawel and comment from 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);
  }
}



回答8:


If you need this functionality, you can customize the builder class by yourself and you can still add @Builder Annotation.

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



回答9:


Take User class as example, id field is required:

@AllArgsConstructor(access = AccessLevel.PRIVATE) // required, see https://stackoverflow.com/questions/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);
    }
}

You can only initialize a User instance by builder like User user = User.builder("id-123").name("Tom").build;. With private no args constructer, you are not able to User user = new User(); or User user = new User("id-123"); so you always need to pass the required parameter id. Please note the initialized instance is immutable.



来源:https://stackoverflow.com/questions/29885428/required-arguments-with-a-lombok-builder

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!