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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM