https://blog.youkuaiyun.com/weixin_74412978/article/details/144455221
@Builder使用遇到的坑
问题现象
问题现象大概如下面示例所示:
有一个Account对象,内部包含一个设置默认值为0的type属性,但由于使用了Builder方式构建,最终导致默认值丢失。
- @Builder
- @Data
- @AllArgsConstructor
- @NoArgsConstructor
- public class Account {
- private Long id;
- private String name;
- private String type = "0";
- }
- // 测试方法
- class Main {
- public static void main(String[] args) {
- Account account = Account.builder().name("Li").id(1L).build();
- System.out.println(account);
- }
- }
测试结果输出:
Account(id=1, name=Li,type=null)
问题分析
通过反编译@Builder注解生成的内容可以看出,问题就在于build方法。
- public static class AccountBuilder {
- private Long id;
- private String name;
- private String type;
- AccountBuilder() {
- }
- public Account.AccountBuilder id(Long id) {
- this.id = id;
- return this;
- }
- public Account.AccountBuilder name(String name) {
- this.name = name;
- return this;
- }
- public Account.AccountBuilder type(String type) {
- this.type = type;
- return this;
- }
- public Account build() {
- return new Account(this.id, this.name, this.type);
- }
- public String toString() {
- return "Account.AccountBuilder(id=" + this.id + ", name=" + this.name + ", type=" + this.type + ")";
- }
- }
解决方式
在有默认值的属性上加上@Builder.Default注解即可。
- @Builder
- @Data
- @AllArgsConstructor
- @NoArgsConstructor
- public class Account {
- private Long id;
- private String name;
- @Builder.Default
- private String type = "0";
- }
再次反编译后发现(已过滤掉了部分无关代码内容),其实现方式就是通过$default$type方法来完成默认值的赋值操作。
- public class Account {
- private Long id;
- private String name;
- private String type;
- // 如果没有主动设置过type属性,则会通过调用此方法完成默认值设置
- private static String $default$type() {
- return "0";
- }
- public static Account.AccountBuilder builder() {
- return new Account.AccountBuilder();
- }
- public Account(Long id, String name, String type) {
- this.id = id;
- this.name = name;
- this.type = type;
- }
- public Account() {
- this.type = $default$type();
- }
- public static class AccountBuilder {
- private Long id;
- private String name;
- private boolean type$set;
- private String type$value;
- AccountBuilder() {
- }
- public Account.AccountBuilder id(Long id) {
- this.id = id;
- return this;
- }
- public Account.AccountBuilder name(String name) {
- this.name = name;
- return this;
- }
- public Account.AccountBuilder type(String type) {
- this.type$value = type;
- this.type$set = true;
- return this;
- }
- public Account build() {
- String type$value = this.type$value;
- if (!this.type$set) {
- type$value = Account.$default$type();
- }
- return new Account(this.id, this.name, type$value);
- }
- public String toString() {
- return "Account.AccountBuilder(id=" + this.id + ", name=" + this.name + ", type$value=" + this.type$value + ")";
- }
- }
- }
延伸思考
实际上@Builder使用还会遇到其他问题
比如如果没有@NoArgsConstructor注解,就会缺少无参的构造方法,这严重违反了代码编写规范。
- 如果只有
@Data注解,会生成无参构造方法。 - 如果只有
@Builder注解,会生成全属性的构造方法,但没有无参构造方法。 - 如果
@Data和@Builder一起使用,哪怕你手动添加了无参构造方法或者添加了@NoArgsConstructor注解,最终在程序运行时依然会报错(编译时无问题)。 - 实际上通常情况下你必须4个注解一起加上使用
@Builder、@Data、@AllArgsConstructor、@NoArgsConstructor。
关于上面这些问题,你可以说是使用者本身对于@Builder注解的理解不深,但笔者认为在实际的业务开发中,难免会因为各种各样的原因而导致问题产生,对于一个方法的使用,不单单是要要求使用者能够完全了解,更重要的是方法本身在使用时会不会出现让使用者容易忽视的条件。
替代方案
我相信大多数使用@Builder主要是因为链式编程所带来的便捷性,实际上笔者更推荐使用@Accessors,它同样可以实现链式编程,同时还可以避免多创建一个Builder对象,更重要的是可以避免@Builder的坑。
只需要通过@Accessors(chain = true)和@Data两个注解即可轻松实现。
- //@Builder
- //@Data
- //@AllArgsConstructor
- //@NoArgsConstructor
- @Accessors(chain = true)
- @Data
- public class Account {
- private Long id;
- private String name;
- // @Builder.Default
- private String type = "0";
- }
- class Main {
- public static void main(String[] args) {
- // Account account = Account.builder().name("Li").id(1L).build();
- // System.out.println(account);
- Account ac = new Account().setId(1L).setName("Li");
- System.out.println(ac);
- }
- }
Lombok @Builder注解致属性默认值丢失问题
1421

被折叠的 条评论
为什么被折叠?



