Lombok以注解形式來簡化java代碼,提高開發效率。比如我們常用的@Builder
、@Data
、@AllArgsConstructor
、@NoArgsConstructor
、@ToString
等。
然最近在迭代中發現Lombok(version:1.16.20
或者低於這個版本)的builder模式與new實例化或者反射機制下實例化對象默認值不兼容。這里的默認值不是基本數據類型
Lombok是通過注解的方式,在編譯時自動為屬性生成構造器、getter/setter、equals、hashcode、toString方法。可以通過反編譯查看生成的字節碼。例子:
@Builder @Data @ToString @AllArgsConstructor @NoArgsConstructor public class A { int num; Integer count; Integer noticedCount = 0; }
使用方式如下
public class Test { public static void main(String[] args) { A a = A.builder().count(1).noticedCount(2).build(); } }
這樣寫看着比以前的new A(),再set值方便多了,當然也可以在構造函數中直接傳入需要的值。但是如果類的屬性多了,就會發現Lombok使用以及開發效率上要高很多。
然而最近,在項目中使用的時候發現一個bug問題,項目中使用的Lombok的版本號1.16.20。如上面的例子,通過A.builder().build()
實例化后,發現a中的noticedCount
的默認值為null。究其原因,查看生成的class文件,有個A$Builder.class,使用javap -c A.class查看字節碼或者直接將這個class文件拖拽到idea中,查看生成的代碼,以下是在idea中展示class的代碼
package com.test; public class A$ABuilder { private int num; private Integer count; private Integer noticedCount; A$ABuilder() { } public A$ABuilder num(int num) { this.num = num; return this; } public A$ABuilder count(Integer count) { this.count = count; return this; } public A$ABuilder noticedCount(Integer noticedCount) { this.noticedCount = noticedCount; return this; } public A build() { return new A(this.num, this.count, this.noticedCount); } public String toString() { return "A.ABuilder(num=" + this.num + ", count=" + this.count + ", noticedCount=" + this.noticedCount + ")"; } }
從中看到noticedCount
默認值沒有。看出A.builder().build()
中的build()方法構造A對象的時候是使用內部類的屬性值,所以這個初始化的實例我們的noticedCount
值為空。
經過查看Lombok下的代碼發現有個@Builder.Default
根據注釋,這個是能解決初始化默認值的。代碼如下
@Builder @Data @ToString @AllArgsConstructor @NoArgsConstructor public class A { int num; Integer count; @Builder.Default Integer noticedCount = 0; }
再看看生成的A$Builder.class文件的內容如下
package com.test; public class A$ABuilder { private int num; private Integer count; private boolean noticedCount$set; private Integer noticedCount; A$ABuilder() { } public A$ABuilder num(int num) { this.num = num; return this; } public A$ABuilder count(Integer count) { this.count = count; return this; } public A$ABuilder noticedCount(Integer noticedCount) { this.noticedCount = noticedCount; this.noticedCount$set = true; return this; } public A build() { Integer noticedCount = this.noticedCount; if (!this.noticedCount$set) { noticedCount = A.access$000(); } return new A(this.num, this.count, noticedCount); } public String toString() { return "A.ABuilder(num=" + this.num + ", count=" + this.count + ", noticedCount=" + this.noticedCount + ")"; } }
可以看到代碼中多了private boolean noticedCount$set
;這個就是確認是否需要設置默認值。
到這一步你以為就完美了嗎??NO.
假如我們在Test方法中增加一行代碼,如下,自己可以試試運行的結果看看輸出的a與a1的結果
public class Test { public static void main(String[] args) { A a = A.builder().count(1).noticedCount(2).build(); System.out.println(a); A a1 = new A(); System.out.println(a1); } }
什么還需要new?有些場景中,比如其他第三方庫使用這個類的時候,就不是通過builder模式來實例化對象,第三方庫一般都是通過反射機制來實例化,然Lombok給我編譯出來的class字節碼已經不再是原有的。所以就出現問題了。
至於Lombok是如何實現的。可以研究下HandleBuilder
.里面有具體邏輯