1. 程式人生 > 其它 >認識lombok 的@Builder註解對初始化的影響

認識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 }