• @Builder使用遇到的坑


    问题现象

    问题现象大概如下面示例,Account对象,有一个type属性,且设置了默认值0,但由于使用了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);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    输出内容

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

    问题分析

    通过反编译可以看出,问题就在于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 + ")";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    解决方式

    加上@Builder.Default注解即可

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

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

    public class Account {
        private Long id;
        private String name;
        private String 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 + ")";
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    延伸思考

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

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

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

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

    替代方案

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

    @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);
            
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
  • 相关阅读:
    Excel Unix时间戳和日期时间格式的相互转换
    javascript回调函数有什么用
    《排序和数据结构学习笔记》看完直呼,太全了!
    个人网页制作 个人网页设计作业 HTML CSS个人网页模板 大学生个人介绍网站毕业设计 DW个人主题网页模板下载 个人网页成品代码 个人网页作品下载
    【开源】基于Vue和SpringBoot的快乐贩卖馆管理系统
    使用C语言实现最小生成树
    学习package.json
    阿里平头哥发布RISC-V高能效处理器玄铁C908,打造端云一体生态
    为什么当下MES如此火热,各大制造业工厂都在推行?
    单元测试(unit testing)到底是什么?
  • 原文地址:https://blog.csdn.net/CSDN_WYL2016/article/details/132838515