【问题标题】:Default value in lombok. How to init default with both constructor and builder龙目岛的默认值。如何使用构造函数和生成器初始化默认值
【发布时间】:2018-06-01 17:00:56
【问题描述】:

我有一个对象

@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 没有获得默认值。我将@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

【问题讨论】:

标签: java lombok


【解决方案1】:

我的猜测是这是不可能的(没有删除代码)。但是你为什么不直接实现你需要的构造函数呢? 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,至少对于生成的构造函数。请注意,当您混合使用 Builder.Default 和显式构造函数时,仍然存在 a similar issue

【讨论】:

  • 小心!目前,此@Builder.Default 已从 v1.16.16 中断 = 它会将您的字段初始化为 null,无论在字段级别设置的指定默认初始化值如何...请参阅 Issue here
  • 解决方案并不完美,因为您有两个将字段初始化两次。一次是在字段声明中,第二次是在构造函数中。它使代码容易出错。我以前用过这个stackoverflow.com/a/50392165/2443502
  • 为了使代码不易出错,我添加了一个 JUnit 测试来验证无​​参数构造函数是否编写得很好:assertThat(new UserInfo().equals(UserInfo.builder().build()).describedAs("some properties marked with @Builder.Default are not properly set in the no-arg constructor").isTrue();
【解决方案2】:

自从@Builder.Default annotation is broken之后,我根本不会使用它。但是,您可以通过将 @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 提供者使用时,当您不是由构建器手动实例化它而是在您背后调用无参数构造函数来创建实例时,这一点尤其重要。

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

【讨论】:

  • 由于 Lombok 有许多怪癖,当您不知道时可能会带来很多麻烦而不是好处,您也可以尝试 Immutables (immutables.github.io) 或 Autovalue (github.com/google/auto/blob/master/value/userguide/builders.md)。
  • 所有其他工具都有一个很大的缺点:它们会生成一个新类。它们的问题肯定更少,因为它们只是使用众所周知(且丑陋的)API 的注释处理器。 Lombok 必须以不同的方式工作,并且确实存在一些错误。但是,我从一开始就在使用它,我对它非常满意。
  • 注释并没有完全损坏,但它与非原始对象存在问题。从版本1.16.22开始,默认值对我有用Boolean
  • 如果您有@Singular 注释,想要知道如何执行此操作,请查看此评论:github.com/rzwitserloot/lombok/issues/… TLDR:将您的@Singular 注释移动到新创建的私有构造函数中。
  • 最新版本的 Lombok 似乎不再损坏它。
【解决方案3】:

另一种方法是定义自己的 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;
    }
}

【讨论】:

  • 不鼓励在基于属性的 getter 中使用逻辑。想象一下你想在类中添加一个额外的方法的情况,该方法依赖于字段(isEmailConfirmed)。实现者直接使用字段或调用 getter 可能会造成混淆。值可能不同,这可能会导致错误。
  • @MarcinKłopotek 你的观点很好,但我觉得可以通过在该私有字段上使用不推荐使用的注释来限制它直接使用来处理。另外,如果我是该类的实现者并且我知道我需要一个默认值并且我已经为它编写了代码,那么显然我应该知道我不应该直接使用该字段来在其他逻辑方法上使用它类。
  • @SahilChhabra 歧义很糟糕。 Deprecated 有一个特殊的目的,除非你想让你的代码的用户更加困惑。最后假设它的“你的班级”和“你应该知道”这个字段不被使用等等还有另一个严重的问题..坚持一致性,你的代码会照顾到两者未来的开发者及其最终用户!
  • @prash 你不可能每次都遵循所有的最佳实践。有时,在使用有缺陷的第三方代码时,您必须有所偏差。您可以选择根本不使用第三方代码,或者通过确保未来的开发人员了解使用情况来选择解决方法(此处已弃用可以很好地完成这项工作)。这只是一种选择和意见。我很高兴你有自己的看法。另外,我建议您再次阅读弃用用法 - docs.oracle.com/javase/7/docs/technotes/guides/javadoc/…
  • @prash 我同意他的回答更好。我只是给出了另一种方法。我同意它有你提到的问题,但这取决于用例。此外,我从未说过任何反对最佳实践的言论。我只是说有时你不能遵循每一个最佳实践(同样取决于用例)。
【解决方案4】:

我的经验是@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");

【讨论】:

    【解决方案5】:

    这是我的方法:

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

    【讨论】:

    • 我使用相同的方法,但不使用:UserInfo ui = new UserInfo().toBuilder().build(); 我更喜欢使用:UserInfo ui = new UserInfo(); 相同的东西,代码更少。
    【解决方案6】:

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

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

    我的处理方法是:

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

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

    【讨论】:

      【解决方案7】:

      初始化 No-Arg Constructor 中的属性

      已转换
      private boolean isEmailConfirmed = true;

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

      【讨论】:

        【解决方案8】:

        您可以创建一个填充了默认值的静态 Builder 类:

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

        【讨论】:

          【解决方案9】:

          自定义构造函数和@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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2015-01-12
            • 2021-12-16
            • 2014-07-25
            • 1970-01-01
            • 2017-05-31
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多