lombok?子類中如何使用@Builder問題
lombok子類中如何使用@Builder
lombok大家都知道,在使用POJO過程中,它給我們帶來了很多便利,省下大量寫get、set方法、構造器、equal、toString方法的時間。除此之外,通過@Builder注解,lombok還可以方便的時間建造者模式。
但是,在使用@Builder過程中,我發(fā)現(xiàn)了一問題:子類的Builder對象沒有父類的屬性。這在使用上造成了一定的問題。
幾番搜索,對于這個問題,找到了如下解法,解法的鏈接會放到文末。
1. 對于父類,使用@AllArgsConstructor注解
2. 對于子類,手動編寫全參數(shù)構造器,內部調用父類全參數(shù)構造器,在子類全參數(shù)構造器上使用@Builder注解
通過這種方式,子類Builder對象可以使用父類的所有私有屬性。
但是這種解法也有兩個副作用:
1. 因為使用AllArgsConstructor注解,父類構造函數(shù)字段的順序由聲明字段的順序決定,如果子類構造函數(shù)傳參的時候順序不一致,字段類型還一樣的話,出了錯不好發(fā)現(xiàn)
2. 如果父類字段有增減,所有子類的構造器都要修改
雖然有這兩個副作用,但是這種解法是我找到的唯一一種解決子類使用@Builder,能使用父類屬性的方式。
參考博客:
Lombok’s @Builder annotation and inheritance
副作用見博客評論
另,這個博主對lombok使用很有心得,我閑看還看到他另一篇涉及到@Builder的文章,將如何在使用@Builder的模式中,加入字段的默認值。因為使用了建造者模式,那么一般在類內聲明字段的時候給字段默認值的方式就是無效的,需要在建造者上動手腳。
方式是:
1. 自定義靜態(tài)內部類作為建造者,賦予默認值,再使用@Builder注解,這個時候lombok會補全已有的建造者類,進而使用默認值
2. 更新的lombok有@Builder.Default聲明,注解在需要默認值的字段上即可。
在評論區(qū)也有這種方式的副作用討論,可以一看。鏈接是:
Using Lombok’s @Builder annotation with default values
子類使用lombok的@Builder注解正確姿勢
在實際開發(fā)中,有時候需要對子類使用lombok的 @Builder注解來使用builder模式構造該子類對象。
父類
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 注解不僅可以用在類上,還可以用在構造函數(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ù)的構造函數(shù)使用 @Builder 注解,會有 BUG,即最終的 build() 函數(shù)只是返回了空參的構造函數(shù)創(chuàng)建了一個 Child 對象,因此屬性“采用 builder 方式設置的 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.
可知,將其加到類上,相當于包含所有屬性的私有構造方法,且構造方法上加上 @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模式的核心邏輯。
即構造內部類,在內部類賦值屬性,build時調用含有所有屬性的構造方法創(chuàng)建對象。
更多細節(jié)可以仔細查看 @Builder 注解的源碼和注釋,查看官方的手冊 https://projectlombok.org/features/Builder
總結:
遇到詭異的問題一定不要輕易放過。分析問題要有步驟,比如可以看源碼中是否有說明,也可以看編譯后的代碼,還可以通過反匯編等,觀察注解對類文件作出了哪些影響。還可以去看官方手冊。
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
SpringBoot整合canal實現(xiàn)數(shù)據(jù)緩存一致性解決方案
canal主要用途是基于?MySQL?數(shù)據(jù)庫增量日志解析,提供增量數(shù)據(jù)訂閱和消費,canal是借助于MySQL主從復制原理實現(xiàn),本文將給大家介紹SpringBoot整合canal實現(xiàn)數(shù)據(jù)緩存一致性解決方案,需要的朋友可以參考下2024-03-03
java遞歸實現(xiàn)樹形結構數(shù)據(jù)完整案例
遞歸算法的代碼比較簡潔,可讀性較好;但是在實際的業(yè)務處理中會出現(xiàn)多次的重復調用,如果處理不好,很容易出現(xiàn)StackOverflowError報錯,這篇文章主要給大家介紹了關于java遞歸實現(xiàn)樹形結構數(shù)據(jù)的相關資料,需要的朋友可以參考下2023-04-04
java工具類SendEmailUtil實現(xiàn)發(fā)送郵件
這篇文章主要為大家詳細介紹了java工具類SendEmailUtil實現(xiàn)發(fā)送郵件,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-02-02
Java 中Timer和TimerTask 定時器和定時任務使用的例子
這篇文章主要介紹了Java 中Timer和TimerTask 定時器和定時任務使用的例子,非常具有實用價值,需要的朋友可以參考下2017-05-05

