认识lombok 的@Builder注解对初始化的影响

参考:https://www.jianshu.com/p/4e4cef2e82e1

参考:https://blog.csdn.net/qq_28411869/article/details/84104893

先上结论:

  • 问题1:如果类中用了@Builder注解,而属性没有任何注解话,那么在你初始化这个类的时候,如果你的属性赋值了默认值,则在你用builder方法初始化该类后,属性的默认值则无效即获取会产生空指针异常
  • 问题2:在具体要赋默认值字段上加@Builder.default注解可以解决问题1,但是际运行代码之后,我发现一个无奈的问题,builder模式下默认值生效了,但是使用new(以及正常反序列化)得到的实例默认值不会被设置

问题1描述:

我们来剖析下这中间发生了什么

从上面的例子,可以发现Teacher 的address属性为空,这正是我们很常规初始化操作,获取这个address,接着对它进行操作, 如果此时它是null,则会出现空指针异常;
      比较Student中的address则是我们理想中的正常执行过程,是有一个默认值的对象,同时观察Student中的name 和age两个属性值默认值也出现了如同Teacher中的address现象,默认值消失了;
      从表面来看,Student多了@Builder.Default的注解,这个注解确实就是解决这个问题关键,让你想要赋值的默认值来进行正确的初始化了。知道了这个注解的使用只是做到了知其然,我们要做做知其所以然,所以来看看下他们生成的class有什么区别? 以下代码反编译删除了equal和hashcode方法

Teacher.class

复制代码
 1 public class Teacher {
 2 private String name;
 3 private List<String> address = new ArrayList();
 4 
 5 Teacher(String name, List<String> address) {
 6     this.name = name;
 7     this.address = address;
 8 }
 9 
10 public static Teacher.TeacherBuilder builder() {
11     return new Teacher.TeacherBuilder();
12 }
13 
14 public String getName() {
15     return this.name;
16 }
17 
18 public List<String> getAddress() {
19     return this.address;
20 }
21 
22 public void setName(String name) {
23     this.name = name;
24 }
25 public void setAddress(List<String> address) {
26     this.address = address;
27 }
28 protected boolean canEqual(Object other) {
29     return other instanceof Teacher;
30 }
31 
32 public String toString() {
33     return "Teacher(name=" + this.getName() + ", address=" + this.getAddress() + ")";
34 }
35 
36 public static class TeacherBuilder {
37     private String name;
38     private List<String> address;
39 
40     TeacherBuilder() {
41     }
42 
43     public Teacher.TeacherBuilder name(String name) {
44         this.name = name;
45         return this;
46     }
47 
48     public Teacher.TeacherBuilder address(List<String> address) {
49         this.address = address;
50         return this;
51     }
52 
53     public Teacher build() {
54         return new Teacher(this.name, this.address);
55     }
56 
57     public String toString() {
58         return "Teacher.TeacherBuilder(name=" + this.name + ", address=" + this.address + ")";
59     }
60 }
复制代码

Student.class

复制代码
public class Student {
private String name = "c";
private int age = 25;
private long num;
private List<String> address;

private static List<String> $default$address() {
    return new ArrayList();
}

Student(String name, int age, long num, List<String> address) {
    this.name = name;
    this.age = age;
    this.num = num;
    this.address = address;
}

public static Student.StudentBuilder builder() {
    return new Student.StudentBuilder();
}

public String getName() {
    return this.name;
}

public int getAge() {
    return this.age;
}

public long getNum() {
    return this.num;
}

public List<String> getAddress() {
    return this.address;
}

public void setName(String name) {
    this.name = name;
}

public void setAge(int age) {
    this.age = age;
}

public void setNum(long num) {
    this.num = num;
}

public void setAddress(List<String> address) {
    this.address = address;
}
protected boolean canEqual(Object other) {
    return other instanceof Student;
}

  public String toString() {
    return "Student(name=" + this.getName() + ", age=" + this.getAge() + ", num=" + this.getNum() + ", address=" + this.getAddress() + ")";
}

public static class StudentBuilder {
    private String name;
    private int age;
    private long num;
    private boolean address$set;
    private List<String> address;

    StudentBuilder() {
    }

    public Student.StudentBuilder name(String name) {
        this.name = name;
        return this;
    }

    public Student.StudentBuilder age(int age) {
        this.age = age;
        return this;
    }

    public Student.StudentBuilder num(long num) {
        this.num = num;
        return this;
    }

    public Student.StudentBuilder address(List<String> address) {
        this.address = address;
        this.address$set = true;
        return this;
    }

    public Student build() {
        List address = this.address;
        if(!this.address$set) {
            address = Student.$default$address();
        }

        return new Student(this.name, this.age, this.num, address);
    }

    public String toString() {
        return "Student.StudentBuilder(name=" + this.name + ", age=" + this.age + ", num=" + this.num + ", address=" + this.address + ")";
    }
}
复制代码

看两个类的build方法,Student类在调用builde方法时,会判断this.address$set 这个变量是否为false,如果为false,则为这个address对象进行赋值默认值,这个变量就是由@Builder.Default注解产生的.
      而如果你直接对address方法进行赋值话,则会将这个this.address$set进行赋值,这样调用build方法时,就不会再对address进行赋值了.
      比较Teacher,没有对address属性增加@Builder.Default注解,所以在调用build方法时候,就不会产生判断是否要对address进行默认值的初始化了,所以你获取到的address就是null.

这下你知道你程序为什么会出现空指针异常了,为什么添加@Builder.Default注解就能解决问题了。所以对你用的东西进行深入了解,出现问题才能做到知其然知其所以然

问题2描述:

贴上测试代码,清晰些。

复制代码
 1 import lombok.AllArgsConstructor;
 2 import lombok.Builder;
 3 import lombok.NoArgsConstructor;
 4 import lombok.ToString;
 5  
 6 public class testLombok {
 7   public static void main(String[] args) {
 8     People p1 = new People();
 9     System.out.println(p1); //People(old=false)
10     People p2 = People.builder().build(); //People(old=true)
11     System.out.println(p2);
12   }
13 }
14  
15  
16 @Builder
17 @ToString
18 @NoArgsConstructor
19 @AllArgsConstructor
20 class People {
21   @Builder.Default
22   private boolean old = true;
23 }
复制代码

为什么会这样呢?心里一万头羊驼跑过...

我们来看反编译后的People代码

复制代码
 1 //
 2 // Source code recreated from a .class file by IntelliJ IDEA
 3 // (powered by Fernflower decompiler)
 4 //
 5  
 6 import java.beans.ConstructorProperties;
 7  
 8 class People {
 9   private boolean old;
10  
11   private static boolean $default$old() {
12     return true;
13   }
14  
15   public static People.PeopleBuilder builder() {
16     return new People.PeopleBuilder();
17   }
18  
19   public String toString() {
20     return "People(old=" + this.old + ")";
21   }
22  
23   public People() {
24   }
25  
26   @ConstructorProperties({"old"})
27   public People(boolean old) {
28     this.old = old;
29   }
30  
31   public static class PeopleBuilder {
32     private boolean old$set;
33     private boolean old;
34  
35     PeopleBuilder() {
36     }
37  
38     public People.PeopleBuilder old(boolean old) {
39       this.old = old;
40       this.old$set = true;
41       return this;
42     }
43  
44     public People build() {
45       return new People(this.old$set ? this.old : People.$default$old());
46     }
47  
48     public String toString() {
49       return "People.PeopleBuilder(old=" + this.old + ")";
50     }
51   }
52 }
复制代码

从最开始看起,代码中old字段没有赋初值,并且多了一个static方法$default$old,方法的返回值即为设置的默认值。接着往后看,在静态内部类PeopleBuilder 中的build方法中对old字段进行了判断,如果没有被设置值,那么就将$default$old方法中的默认值赋给People实例。现在终于明白之前的困惑,并且顺带了解了@Builder.Default的实现原理。

既然@Builder.Default没有办法解决问题,那么该怎么办呢?

可以换个思路,实例初始化的时候,boolean字段会被初始化为false,利用这个特性把字段名字改为notOld即可。代码如下

1 public class People {
2   private boolean notOld;
3 }

 

posted @   Boblim  阅读(1316)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
历史上的今天:
2017-03-09 程序编译是出现"field has incomplete type"问题的解决
点击右上角即可分享
微信分享提示