lombok?子類中如何使用@Builder問題
lombok子類中如何使用@Builder
lombok大家都知道,在使用POJO過程中,它給我們帶來了很多便利,省下大量寫get、set方法、構(gòu)造器、equal、toString方法的時間。除此之外,通過@Builder注解,lombok還可以方便的時間建造者模式。
但是,在使用@Builder過程中,我發(fā)現(xiàn)了一問題:子類的Builder對象沒有父類的屬性。這在使用上造成了一定的問題。
幾番搜索,對于這個問題,找到了如下解法,解法的鏈接會放到文末。
1. 對于父類,使用@AllArgsConstructor注解
2. 對于子類,手動編寫全參數(shù)構(gòu)造器,內(nèi)部調(diào)用父類全參數(shù)構(gòu)造器,在子類全參數(shù)構(gòu)造器上使用@Builder注解
通過這種方式,子類Builder對象可以使用父類的所有私有屬性。
但是這種解法也有兩個副作用:
1. 因為使用AllArgsConstructor注解,父類構(gòu)造函數(shù)字段的順序由聲明字段的順序決定,如果子類構(gòu)造函數(shù)傳參的時候順序不一致,字段類型還一樣的話,出了錯不好發(fā)現(xiàn)
2. 如果父類字段有增減,所有子類的構(gòu)造器都要修改
雖然有這兩個副作用,但是這種解法是我找到的唯一一種解決子類使用@Builder,能使用父類屬性的方式。
參考博客:
Lombok’s @Builder annotation and inheritance
副作用見博客評論
另,這個博主對lombok使用很有心得,我閑看還看到他另一篇涉及到@Builder的文章,將如何在使用@Builder的模式中,加入字段的默認值。因為使用了建造者模式,那么一般在類內(nèi)聲明字段的時候給字段默認值的方式就是無效的,需要在建造者上動手腳。
方式是:
1. 自定義靜態(tài)內(nèi)部類作為建造者,賦予默認值,再使用@Builder注解,這個時候lombok會補全已有的建造者類,進而使用默認值
2. 更新的lombok有@Builder.Default聲明,注解在需要默認值的字段上即可。
在評論區(qū)也有這種方式的副作用討論,可以一看。鏈接是:
Using Lombok’s @Builder annotation with default values
子類使用lombok的@Builder注解正確姿勢
在實際開發(fā)中,有時候需要對子類使用lombok的 @Builder注解來使用builder模式構(gòu)造該子類對象。
父類
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor public class Parent { private Long id; private String name; }
子類
import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; @EqualsAndHashCode(callSuper = true) @Data @NoArgsConstructor @Builder public class Child extends Parent{ }
此時雖然在子類上添加了@Builder注解,但是由于子類沒有屬性,如下圖所示,無法使用builder模式。
分析一下
通過閱讀 lombok.Builder的源碼,可知 @Builder 注解不僅可以用在類上,還可以用在構(gòu)造函數(shù)上。
因此嘗試如下寫法:
@EqualsAndHashCode(callSuper = true) @Data @NoArgsConstructor @Builder public class Child extends Parent { @Builder private Child(Long id, String name) { super(id, name); } }
再次運行上面的單元測試,發(fā)現(xiàn)支持了 builder 模式,但是奇怪的是,單測不通過。
java.lang.AssertionError:
Expected :1024
Actual :null
因此我們觀察一下 Child.class 反編譯后的代碼:
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package com.chujianyun.libs.lombok; public class Child extends Parent { private Child(Long id, String name) { super(id, name); } public static Child.ChildBuilder builder() { return new Child.ChildBuilder(); } public boolean equals(final Object o) { if (o == this) { return true; } else if (!(o instanceof Child)) { return false; } else { Child other = (Child)o; if (!other.canEqual(this)) { return false; } else { return super.equals(o); } } } protected boolean canEqual(final Object other) { return other instanceof Child; } public int hashCode() { int result = super.hashCode(); return result; } public String toString() { return "Child()"; } public Child() { } public static class ChildBuilder { private Long id; private String name; ChildBuilder() { } public Child build() { return new Child(); } public String toString() { return "Child.ChildBuilder()"; } public Child.ChildBuilder id(final Long id) { this.id = id; return this; } public Child.ChildBuilder name(final String name) { this.name = name; return this; } } }
找到了原因,同時在子類和全參數(shù)的構(gòu)造函數(shù)使用 @Builder 注解,會有 BUG,即最終的 build() 函數(shù)只是返回了空參的構(gòu)造函數(shù)創(chuàng)建了一個 Child 對象,因此屬性“采用 builder 方式設(shè)置的 id 和 name” 最終“丟失”。
那么如何解決這個問題呢?
我們再次回到@Builder 源碼的注釋上:
If a member is annotated, it must be either a constructor or a method. If a class is annotated,* then a private constructor is generated with all fields as arguments* (as if {@code @AllArgsConstructor(access = AccessLevel.PRIVATE)} is present* on the class), and it is as if this constructor has been annotated with {@code @Builder} instead.
可知,將其加到類上,相當(dāng)于包含所有屬性的私有構(gòu)造方法,且構(gòu)造方法上加上 @Builder 注解。
因此我們寫的代碼可能有沖突,我們修改如下:
import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; @EqualsAndHashCode(callSuper = true) @Data @NoArgsConstructor public class Child extends Parent { @Builder private Child(Long id, String name) { super(id, name); } }
最終單測通過
我們觀察一下此時編譯后的代碼:
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package com.chujianyun.libs.lombok; public class Child extends Parent { private Child(Long id, String name) { super(id, name); } public static Child.ChildBuilder builder() { return new Child.ChildBuilder(); } public boolean equals(final Object o) { if (o == this) { return true; } else if (!(o instanceof Child)) { return false; } else { Child other = (Child)o; if (!other.canEqual(this)) { return false; } else { return super.equals(o); } } } protected boolean canEqual(final Object other) { return other instanceof Child; } public int hashCode() { int result = super.hashCode(); return result; } public String toString() { return "Child()"; } public Child() { } public static class ChildBuilder { private Long id; private String name; ChildBuilder() { } public Child.ChildBuilder id(final Long id) { this.id = id; return this; } public Child.ChildBuilder name(final String name) { this.name = name; return this; } public Child build() { return new Child(this.id, this.name); } public String toString() { return "Child.ChildBuilder(id=" + this.id + ", name=" + this.name + ")"; } } }
此時的build() 函數(shù)才是我們需要的狀態(tài)。
從編譯后的代碼我們可以清晰地看出 lombok 通過@Builder 實現(xiàn)的 builder模式的核心邏輯。
即構(gòu)造內(nèi)部類,在內(nèi)部類賦值屬性,build時調(diào)用含有所有屬性的構(gòu)造方法創(chuàng)建對象。
更多細節(jié)可以仔細查看 @Builder 注解的源碼和注釋,查看官方的手冊 https://projectlombok.org/features/Builder
總結(jié):
遇到詭異的問題一定不要輕易放過。分析問題要有步驟,比如可以看源碼中是否有說明,也可以看編譯后的代碼,還可以通過反匯編等,觀察注解對類文件作出了哪些影響。還可以去看官方手冊。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot整合canal實現(xiàn)數(shù)據(jù)緩存一致性解決方案
canal主要用途是基于?MySQL?數(shù)據(jù)庫增量日志解析,提供增量數(shù)據(jù)訂閱和消費,canal是借助于MySQL主從復(fù)制原理實現(xiàn),本文將給大家介紹SpringBoot整合canal實現(xiàn)數(shù)據(jù)緩存一致性解決方案,需要的朋友可以參考下2024-03-03java遞歸實現(xiàn)樹形結(jié)構(gòu)數(shù)據(jù)完整案例
遞歸算法的代碼比較簡潔,可讀性較好;但是在實際的業(yè)務(wù)處理中會出現(xiàn)多次的重復(fù)調(diào)用,如果處理不好,很容易出現(xiàn)StackOverflowError報錯,這篇文章主要給大家介紹了關(guān)于java遞歸實現(xiàn)樹形結(jié)構(gòu)數(shù)據(jù)的相關(guān)資料,需要的朋友可以參考下2023-04-04java工具類SendEmailUtil實現(xiàn)發(fā)送郵件
這篇文章主要為大家詳細介紹了java工具類SendEmailUtil實現(xiàn)發(fā)送郵件,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-02-02Java 中Timer和TimerTask 定時器和定時任務(wù)使用的例子
這篇文章主要介紹了Java 中Timer和TimerTask 定時器和定時任務(wù)使用的例子,非常具有實用價值,需要的朋友可以參考下2017-05-05