使用lombok的@Builder注解导致属性默认值丢失

Lombok @Builder注解致属性默认值丢失问题

https://blog.youkuaiyun.com/weixin_74412978/article/details/144455221

@Builder使用遇到的坑

问题现象

问题现象大概如下面示例所示:

有一个Account对象,内部包含一个设置默认值0type属性,但由于使用了Builder方式构建,最终导致默认值丢失。        

  1. @Builder
  2. @Data
  3. @AllArgsConstructor
  4. @NoArgsConstructor
  5. public class Account {
  6.     private Long id;
  7.     private String name;
  8.     private String type = "0";
  9. }
  10. // 测试方法
  11. class Main {
  12.     public static void main(String[] args) {
  13.         Account account = Account.builder().name("Li").id(1L).build();
  14.         System.out.println(account);
  15.     }
  16. }

测试结果输出:

Account(id=1, name=Li, type=null)

问题分析

通过反编译@Builder注解生成的内容可以看出,问题就在于build方法。

  1. public static class AccountBuilder {
  2.     private Long id;
  3.     private String name;
  4.     private String type;
  5.     AccountBuilder() {
  6.     }
  7.     public Account.AccountBuilder id(Long id) {
  8.         this.id = id;
  9.         return this;
  10.     }
  11.     public Account.AccountBuilder name(String name) {
  12.         this.name = name;
  13.         return this;
  14.     }
  15.     public Account.AccountBuilder type(String type) {
  16.         this.type = type;
  17.         return this;
  18.     }
  19.     
  20.     public Account build() {
  21.         return new Account(this.id, this.name, this.type);
  22.     }
  23.     public String toString() {
  24.         return "Account.AccountBuilder(id=" + this.id + ", name=" + this.name + ", type=" + this.type + ")";
  25.     }
  26. }

解决方式

在有默认值的属性上加上@Builder.Default注解即可。

  1. @Builder
  2. @Data
  3. @AllArgsConstructor
  4. @NoArgsConstructor
  5. public class Account {
  6.     private Long id;
  7.     private String name;
  8.     @Builder.Default
  9.     private String type = "0";
  10. }

再次反编译后发现(已过滤掉了部分无关代码内容),其实现方式就是通过$default$type方法来完成默认值的赋值操作。

  1. public class Account {
  2.     private Long id;
  3.     private String name;
  4.     private String type;
  5.     
  6.     // 如果没有主动设置过type属性,则会通过调用此方法完成默认值设置
  7.     private static String $default$type() {
  8.         return "0";
  9.     }
  10.     public static Account.AccountBuilder builder() {
  11.         return new Account.AccountBuilder();
  12.     }
  13.     public Account(Long id, String name, String type) {
  14.         this.id = id;
  15.         this.name = name;
  16.         this.type = type;
  17.     }
  18.     public Account() {
  19.         this.type = $default$type();
  20.     }
  21.     public static class AccountBuilder {
  22.         private Long id;
  23.         private String name;
  24.         private boolean type$set;
  25.         private String type$value;
  26.         AccountBuilder() {
  27.         }
  28.         public Account.AccountBuilder id(Long id) {
  29.             this.id = id;
  30.             return this;
  31.         }
  32.         public Account.AccountBuilder name(String name) {
  33.             this.name = name;
  34.             return this;
  35.         }
  36.         public Account.AccountBuilder type(String type) {
  37.             this.type$value = type;
  38.             this.type$set = true;
  39.             return this;
  40.         }
  41.         public Account build() {
  42.             String type$value = this.type$value;
  43.             if (!this.type$set) {
  44.                 type$value = Account.$default$type();
  45.             }
  46.             return new Account(this.id, this.name, type$value);
  47.         }
  48.         public String toString() {
  49.             return "Account.AccountBuilder(id=" + this.id + ", name=" + this.name + ", type$value=" + this.type$value + ")";
  50.         }
  51.     }
  52. }

延伸思考

实际上@Builder使用还会遇到其他问题

比如如果没有@NoArgsConstructor注解,就会缺少无参的构造方法,这严重违反了代码编写规范。

  • 如果只有@Data注解,会生成无参构造方法。
  • 如果只有@Builder注解,会生成全属性的构造方法,但没有无参构造方法。
  • 如果@Data@Builder一起使用,哪怕你手动添加了无参构造方法或者添加了@NoArgsConstructor注解,最终在程序运行时依然会报错(编译时无问题)。
  • 实际上通常情况下你必须4个注解一起加上使用@Builder、@Data、@AllArgsConstructor、@NoArgsConstructor

关于上面这些问题,你可以说是使用者本身对于@Builder注解的理解不深,但笔者认为在实际的业务开发中,难免会因为各种各样的原因而导致问题产生,对于一个方法的使用,不单单是要要求使用者能够完全了解,更重要的是方法本身在使用时会不会出现让使用者容易忽视的条件。

替代方案

我相信大多数使用@Builder主要是因为链式编程所带来的便捷性,实际上笔者更推荐使用@Accessors,它同样可以实现链式编程,同时还可以避免多创建一个Builder对象,更重要的是可以避免@Builder的坑。

只需要通过@Accessors(chain = true)@Data两个注解即可轻松实现。

  1. //@Builder
  2. //@Data
  3. //@AllArgsConstructor
  4. //@NoArgsConstructor
  5. @Accessors(chain = true)
  6. @Data
  7. public class Account {
  8.     private Long id;
  9.     private String name;
  10.     // @Builder.Default
  11.     private String type = "0";
  12. }
  13. class Main {
  14.     public static void main(String[] args) {
  15. //        Account account = Account.builder().name("Li").id(1L).build();
  16. //        System.out.println(account);
  17.         
  18.         Account ac = new Account().setId(1L).setName("Li");
  19.         System.out.println(ac);
  20.         
  21.     }
  22. }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值