通過lombok帶你讀透Builder構(gòu)建器
很久之前,我在《effective java》上看過Builder構(gòu)建器相關(guān)的內(nèi)容,但實際開發(fā)中不經(jīng)常用。后來,在項目中使用了lombok,發(fā)現(xiàn)它有一個注解“@Builder”,就是為java bean生成一個構(gòu)建器。于是,回頭重新復(fù)習(xí)了下相關(guān)知識,整理如下。
1. lombok使用樣例
// 創(chuàng)建名為Officer的java bean
@Builder
public class Officer {
private final String id;
private final String name;
private final int age;
private final String department;
}
// 調(diào)用構(gòu)建器生成Officer實例
class BuilderTest {
public static void main(String[] args) {
Officer officer = Officer.builder().id("00001").name("simon qi")
.age(26).department("departmentA").build();
}
}
2. 反編譯lombok生成的Officer.class
注意:下面請區(qū)分兩組名詞:"builder方法"和“build方法”,“構(gòu)造器”和“構(gòu)建器”。
public class Officer {
private final String id;
private final String name;
private final int age;
private final String department;
Officer(String id, String name, int age, String department) {
this.id = id;
this.name = name;
this.age = age;
this.department = department;
}
public static Officer.OfficerBuilder builder() {
return new Officer.OfficerBuilder();
}
public static class OfficerBuilder {
private String id;
private String name;
private int age;
private String department;
OfficerBuilder() {
}
public Officer.OfficerBuilder id(String id) {
this.id = id;
return this;
}
public Officer.OfficerBuilder name(String name) {
this.name = name;
return this;
}
public Officer.OfficerBuilder age(int age) {
this.age = age;
return this;
}
public Officer.OfficerBuilder department(String department) {
this.department = department;
return this;
}
public Officer build() {
return new Officer(this.id, this.name, this.age, this.department);
}
public String toString() {
return "Officer.OfficerBuilder(id=" + this.id + ", name=" + this.name + ", age=" + this.age + ", department=" + this.department + ")";
}
}
}
我們通過反編譯Officer.class,獲得上方的源碼(最好用idea自帶的反編譯器,jd-gui反編譯的源碼不全)。
我們發(fā)現(xiàn)源碼中有一個OfficerBuilder的靜態(tài)內(nèi)部類,我們在調(diào)用builder方法時,實際返回了這個靜態(tài)內(nèi)部類的實例。這個OfficerBuilder類,具有和Officer相同的成員變量,且擁有名為id,name,age和department的方法。這些以O(shè)fficer的成員變量命名的方法,都是給OfficerBuilder的成員變量賦值,并返回this。
這些方法返回this,其實就是返回調(diào)用這些方法的OfficerBuilder對象,也可稱為“返回對象本身”。通過返回對象本身,形成了方法的鏈?zhǔn)秸{(diào)用。
再看build方法,它是OfficerBuilder類的方法。它創(chuàng)建了一個新的Officer對象,并將自身的成員變量值,傳給了Officer的成員變量。所以Officer officer = Officer.builder().id("00001").name("simon qi").age(26).department("departmentA").build();的寫法,等價于下面的寫法:
Officer.OfficerBuilder officerBuilder = new Officer.OfficerBuilder();
officerBuilder.id("00001").name("simon qi").age(26).department("departmentA");
Officer officer = officerBuilder.build();
所以為什么這種模式叫“構(gòu)建器”,因為要創(chuàng)建Officer類的實例,首先要創(chuàng)建OfficerBuilder類的實例。而這個OfficerBuilder也就是構(gòu)建器,是創(chuàng)建Officer對象的一個過渡者。所以利用這種模式,會有中間實例的創(chuàng)建,會加大虛擬機(jī)內(nèi)存的消耗。
3. 只用@Builder注解的bug
我們只用@Builder注解,我發(fā)現(xiàn)lombok為Officer類生成的構(gòu)造器是“default”的(不添加權(quán)限修飾符,默認(rèn)為“default”的)。
我們之所以用構(gòu)建器模式,是希望用戶用構(gòu)建器提供的方法去創(chuàng)建實例。但“default”的構(gòu)造器,可以被同package的類調(diào)用(default限制不同package類的調(diào)用)。所以,我們需要將此構(gòu)造器設(shè)為private的。這時就需要用到“@AllArgsConstructor(access = AccessLevel.PRIVATE)”。我們這時再看反編譯后的構(gòu)造器:
private Officer(String id, String name, int age, String department) {
this.id = id;
this.name = name;
this.age = age;
this.department = department;
}
所以,使用lombok的構(gòu)建器,應(yīng)將“@Builder”和“@AllArgsConstructor(access = AccessLevel.PRIVATE)”相結(jié)合,最終寫法:
@Builder
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class Officer {
private final String id;
private final String name;
private final int age;
private final String department;
}
4. 為什么使用構(gòu)建器模式
若一個類具有大量的成員變量,我們就需要提供一個全參的構(gòu)造器或大量的set方法。這讓實例的創(chuàng)建和賦值,變得很麻煩,且不直觀。我們通過構(gòu)建器,可以讓變量的賦值變成鏈?zhǔn)秸{(diào)用,而且調(diào)用的方法名對應(yīng)著成員變量的名稱。讓對象的創(chuàng)建和賦值都變得很簡潔、直觀。
5. 鏈?zhǔn)椒椒ㄙx值,一定要用構(gòu)建器模式嗎?
不一定要用到構(gòu)建器模式,之所以使用構(gòu)建器模式,是因為我們要創(chuàng)造的對象是一個成員變量不可變的對象。
你返回去看Officer類和OfficerBuilder類,你會發(fā)現(xiàn)Officer類的成員變量都是final的,而OfficerBuilder的成員變量卻沒用final修飾。因為final修飾的成員變量,需要在實例創(chuàng)建時就把值確定下來。但在類具有大量成員變量的時候,我們是不希望用戶直接調(diào)用全參構(gòu)造器的。
所以我們使用了OfficerBuilder的中間類。這個類為了實現(xiàn)鏈?zhǔn)劫x值,才將變量設(shè)為非final的。無論你OfficerBuilder實例怎么賦值,怎么改變,當(dāng)你調(diào)用build方法時,就會返回一個成員變量不可變的Officer實例。
那如果有大量屬性,但不需要它是成員變量不可變的對象,我們還需要構(gòu)建器模式嗎?答案是,不需要,我們可以參考構(gòu)建器,把代碼賦值改成鏈?zhǔn)降募纯桑?/p>
public class Officer {
private String id;
private String name;
private int age;
private String department;
public static Officer build() {
return new Officer();
}
private Officer() {
}
public Officer id(String id) {
this.id = id;
return this;
}
public Officer name(String name) {
this.name = name;
return this;
}
public Officer age(int age) {
this.age = age;
return this;
}
public Officer department(String department) {
this.department = department;
return this;
}
}
調(diào)用樣式:
Officer officer = Officer.build().id("00001").name("simon qi").age(26).department("departmentA");
其實這時候構(gòu)造器設(shè)為非private也行,寫成private,只是為了調(diào)用build()顯得更好看。
將構(gòu)造器設(shè)為非private,可以寫為如下形式:
Officer officer = new Officer().id("00001").name("simon qi").age(26).department("departmentA");
所以,我覺得你在使用lombok的"@Builder"注解的時候,還是要思考一下。當(dāng)你不需要成員變量不可變的時候,你完全沒必要使用構(gòu)建器模式,因為這會消耗java虛擬機(jī)的內(nèi)存。
6. 總結(jié)
所以,我一直推薦學(xué)習(xí)知識,要在項目中去學(xué)習(xí)。通過項目,去探索項目以外的知識點,才是提升自己的快捷方法。而且知識不能學(xué)死了,不能網(wǎng)上有哪些知識點,我們就只考慮這些知識點。我們要去思考一些別人不常想到的問題。比如,我們?yōu)槭裁匆弥虚g類去做過渡,這么寫的目的是什么。
將上述知識吃透,面試應(yīng)對構(gòu)建器的時候,也就得心應(yīng)手了。而且通過實戰(zhàn)去回答問題,也更能彰顯你是個愛思考的員工。
作者:永不言Qi
QQ: 591232672
e-mail:591232672@qq.com
版權(quán)聲明:轉(zhuǎn)載請保留此鏈接,不得用于商業(yè)用途。
雖然我不是最優(yōu)秀的程序員,但我還是想盡自己最大的努力,去分享一些學(xué)習(xí)心得。
如有錯誤,歡迎指正。若有幸能博得您的喜愛,歡迎關(guān)注及點贊哦。
愿我們共同進(jìn)步!