龙目岛的默认值。如何使用构造函数和构建器初始化默认值

Default value in lombok. How to init default with both constructor and builder

我有对象

@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

我的猜测是这是不可能的(如果没有删除代码)。但是你为什么不直接实现你需要的构造函数呢? Lombok 旨在让您的生活更轻松,如果某些事情不适用于 Lombok,只需按照老式的方式进行即可。

@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

更新
自 01/2021 起,this bug seems to be fixed in Lombok, at least for generated constructors. Note that there is still a similar issue 当您混合使用 Builder.Default 和显式构造函数时。

另一种方法是定义您自己的 getter 方法 覆盖 lombok getter:

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

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

自从@Builder.Default annotation is broken之后,我就再也不用它了。但是,您可以通过将 @Builder 注释从 class 级别移动到自定义构造函数来使用以下方法:

@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 class 的初始化方式与使用构建器或无参数构造函数的方式相同

也就是说,条件成立true:

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

那样的话,不管你怎么创建,对象的创建都是一致的。当您的 class 被映射框架或 JPA 提供程序使用时,当您没有由构建器手动实例化它,而是在您背后调用无参数构造函数来创建实例时,这一点尤其重要。

方法非常相似,但它有一个主要缺点。您必须在两个地方初始化该字段,这使得代码容易出错,因为您需要保持值一致。

这是我的方法:

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

自定义构造函数和 @Builder.Default 可能永远无法协同工作。

框架作者希望避免 @Builder.

的双重初始化

我通过 public static CLAZZ of(...) 方法重用 .builder()

@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

我的经验是 @Builder 在它是实例化 class 的唯一方法时效果最好,因此与 @Value 而不是 @Data 配对时效果最好.

对于 classes,其中所有字段无论如何都可以按任何顺序可变,并且您希望为其保留链式调用,请考虑将其替换为 @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");

初始化 No-Arg 中的属性 Constructor

已转换
private boolean isEmailConfirmed = true;

public class UserInfo {

    public UserInfo() {
        this.isEmailConfirmed = true;
    }

}

在版本 1.18.2 中,@NoArgsConstructor@Builder 都可以,但不完全。

具有一个或多个字段的构造函数将使所有其他默认初始化为空:new UserInfo("Some nick") 将导致 isEmailConfirmed 再次为假。

我的处理方式是:

public UserInfo(String nick) {
  this();
  this.nick = nick;
}

这样所有默认字段都将被初始化,我们将获得预期的构造函数。

您可以创建静态生成器 class 并填充默认值:

@Data
@Builder(builderClassName="Builder")
@NoArgsConstructor
@AllArgsConstructor
public class UserInfo {
    private int id;
    private String nick;
    private boolean isEmailConfirmed;
    public static class Builder{
          //Set defaults here
          private boolean isEmailConfirmed = true;
    }
}