參考: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 }