Значение по умолчанию в ломбоке. Как инициировать дефолт с помощью как конструктора, так и строителя

У меня есть объект

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserInfo {
    private int id;
    private String nick;
    private boolean isEmailConfirmed = true;
}

И я инициализирую его двумя способами

UserInfo ui = new UserInfo();
UserInfo ui2 = UserInfo.builder().build();

System.out.println("ui: " + ui.isEmailConfirmed());
System.out.println("ui2: " + ui2.isEmailConfirmed());

Вот вывод

ui: true
ui2: false

Кажется, что строитель не получает значение по умолчанию. Я добавляю аннотацию @Builder.Default к моему свойству, и теперь мой объект выглядит так

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserInfo { 
    private int id;
    private String nick;
    @Builder.Default
    private boolean isEmailConfirmed = true;
}

Вот консольный вывод

ui: false
ui2: true

Как я могу заставить их обоих быть true?

Ответ 1

Я предполагаю, что это невозможно (без использования кода). Но почему бы вам просто не реализовать конструктор, в котором вы нуждаетесь? Ломбок призван облегчить вашу жизнь, и если что-то не с Ломбоком не будет работать, просто сделайте это по-старому.

@Data
@Builder
@AllArgsConstructor
public class UserInfo { 
    private int id;
    private String nick;
    @Builder.Default
    private boolean isEmailConfirmed = true;

    public UserInfo(){
        isEmailConfirmed = true;
    }
}

Консольный выход:

ui: true
ui2: true

Ответ 2

Поскольку аннотация @Builder.Default не работает, я бы ее вообще не использовал. Однако вы можете использовать следующий подход, переместив аннотацию @Builder с уровня класса в пользовательский конструктор:

@Data
@NoArgsConstructor
public class UserInfo {

    private int id;
    private String nick;
    private boolean isEmailConfirmed = true;

    @Builder
    @SuppressWarnings("unused")
    private UserInfo(int id, String nick, Boolean isEmailConfirmed) {
        this.id = id;
        this.nick = nick;
        this.isEmailConfirmed = Optional.ofNullable(isEmailConfirmed).orElse(this.isEmailConfirmed);
    }
}

Таким образом, вы гарантируете:

  • поле isEmailConfirmed инициализируется только в одном месте, что делает код менее подверженным ошибкам и облегчает поддержку в дальнейшем
  • класс UserInfo будет инициализирован так же, как вы используете конструктор или конструктор без аргументов

Другими словами, условие выполняется true:

new UserInfo().equals(UserInfo.builder().build())

В этом случае создание объекта не зависит от способа его создания. Это особенно важно, когда ваш класс используется каркасом отображения или провайдером JPA, когда вы не создаете его вручную создателем, но конструктор без аргументов вызывается за вашей спиной для создания экземпляра.

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

Ответ 3

Другим способ определить свой собственный метод получения переопределяет сорбент Ломбок:

@Data
@Builder
@AllArgsConstructor
public class UserInfo { 
    private int id;
    private String nick;
    private Boolean isEmailConfirmed;

    public Boolean getIsEmailConfirmed(){
      return Objects.isNull(isEmailConfirmed) ? true : isEmailConfirmed;
    }
}

Ответ 4

Вот мой подход:

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder(toBuilder = true)
public class UserInfo { 
    private int id;
    private String nick;
    private boolean isEmailConfirmed = true;
}

А потом

UserInfo ui = new UserInfo().toBuilder().build();

Ответ 5

Мой опыт показывает, что @Builder работает лучше всего, когда он является единственным средством создания экземпляра класса, и поэтому лучше всего работает в паре с @Value, а не с @Data.

Для классов, где все поля в любом порядке являются изменяемыми и для которых вы хотите сохранить связанные вызовы, рассмотрите возможность замены их на @Accessors(chain=true) или @Accessors(fluent=true).

@Data
@Accessors(fluent=true)
public class UserInfo {
    private int id;
    private String nick;
    private boolean isEmailConfirmed = true;
}

Это позволяет вам свободно создавать свои объекты в коде и избегать ненужного создания объектов Builder:

UserInfo ui = new UserInfo().id(25).nick("John");

Ответ 6

Пользовательские конструкторы и @Builder.Default вероятно, никогда не будут работать вместе.

Авторы фреймворка хотят избежать двойной инициализации @Builder.

Я повторно использую .builder() public static CLAZZ of(...):

@Builder
public class Connection {
    private String user;
    private String pass;

    @Builder.Default
    private long timeout = 10_000;

    @Builder.Default
    private String port = "8080";

    public static Connection of(String user, String pass) {
        return Connection.builder()
            .user(user)
            .pass(pass)
            .build();
    }

    public static Connection of(String user, String pass, String port) {
        return Connection.builder()
            .user(user)
            .pass(pass)
            .port(port)
            .build();
    }

    public static Connection of(String user, String pass, String port, long timeout) {
        return Connection.builder()
            .user(user)
            .pass(pass)
            .port(port)
            .timeout(timeout)
            .build();
    }
}

Проверьте соответствующее обсуждение: https://github.com/rzwitserloot/lombok/issues/1347