Подтвердить что ты не робот

Необходимые аргументы с lombok @Builder

Если я добавлю @Builder в класс. Создается метод построения.

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

У меня есть требование, когда требуется конкретное поле. В этом случае поле имени требуется, но фамилия не указана. В идеале я хотел бы заявить об этом так.

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

Я не могу понять, как это сделать. Я попытался добавить @Builder к конструктору, но это не сработало.

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

Ответ 1

Вы можете легко сделать это с помощью аннотации 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

Я бы рекомендовал против этого подхода, так как вы будете стараться применять его последовательно на других объектах. Вместо этого вы можете просто пометить поля аннотацией @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()

Ответ 3

Здесь другой подход:

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

Ответ 4

Отвечая на вопрос Кевина Дея, сделайте еще один шаг:

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

}

Это не идеальный вариант, но он обеспечивает принудительное выполнение времени компиляции, и у вызывающих этого класса будет только один метод компоновщика.

Ответ 5

Это мое решение проблемы

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/

Ответ 6

Самое простое решение - добавить @lombok.NonNull ко всем обязательным значениям. Строитель не сможет построить объект, если не установлены обязательные поля.

Вот тест 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;
    }
}

Ответ 7

Если вам нужна эта функциональность, вы можете настроить класс построителя самостоятельно и добавить аннотацию @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);
    }
}

Ответ 8

В качестве примера возьмем класс User, поле id обязательно для заполнения:

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

Вы можете только инициализировать экземпляр User с помощью компоновщика, например User user = User.builder("id-123").name("Tom").build;. При использовании конструктора private no args вы не можете использовать User user = new User(); или User user = new User("id-123");, поэтому вам всегда нужно передавать требуемый параметр id. Обратите внимание, что инициализированный экземпляр является неизменным.

Ответ 9

Объединяя ответ от @Pawel и комментарий от Макса...

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