认识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 }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
2017-03-09 程序编译是出现"field has incomplete type"问题的解决