通過(guò)lombok帶你讀透Builder構(gòu)建器
很久之前涵但,我在《effective java》上看過(guò)Builder構(gòu)建器相關(guān)的內(nèi)容叨吮,但實(shí)際開(kāi)發(fā)中不經(jīng)常用形纺。后來(lái)薇缅,在項(xiàng)目中使用了lombok,發(fā)現(xiàn)它有一個(gè)注解“@Builder”危彩,就是為java bean生成一個(gè)構(gòu)建器。于是泳桦,回頭重新復(fù)習(xí)了下相關(guān)知識(shí)汤徽,整理如下。
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實(shí)例
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ǐng)區(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 + ")";
}
}
}
我們通過(guò)反編譯Officer.class,獲得上方的源碼(最好用idea自帶的反編譯器浮毯,jd-gui反編譯的源碼不全)完疫。
我們發(fā)現(xiàn)源碼中有一個(gè)OfficerBuilder的靜態(tài)內(nèi)部類,我們?cè)谡{(diào)用builder方法時(shí)债蓝,實(shí)際返回了這個(gè)靜態(tài)內(nèi)部類的實(shí)例壳鹤。這個(gè)OfficerBuilder類,具有和Officer相同的成員變量惦蚊,且擁有名為id,name,age和department的方法。這些以O(shè)fficer的成員變量命名的方法讯嫂,都是給OfficerBuilder的成員變量賦值蹦锋,并返回this。
這些方法返回this欧芽,其實(shí)就是返回調(diào)用這些方法的OfficerBuilder對(duì)象莉掂,也可稱為“返回對(duì)象本身”。通過(guò)返回對(duì)象本身千扔,形成了方法的鏈?zhǔn)秸{(diào)用憎妙。
再看build方法,它是OfficerBuilder類的方法曲楚。它創(chuàng)建了一個(gè)新的Officer對(duì)象厘唾,并將自身的成員變量值,傳給了Officer的成員變量龙誊。所以Officer officer = Officer.builder().id("00001").name("simon qi").age(26).department("departmentA").build();
的寫法抚垃,等價(jià)于下面的寫法:
Officer.OfficerBuilder officerBuilder = new Officer.OfficerBuilder();
officerBuilder.id("00001").name("simon qi").age(26).department("departmentA");
Officer officer = officerBuilder.build();
所以為什么這種模式叫“構(gòu)建器”,因?yàn)橐獎(jiǎng)?chuàng)建Officer類的實(shí)例,首先要?jiǎng)?chuàng)建OfficerBuilder類的實(shí)例。而這個(gè)OfficerBuilder也就是構(gòu)建器鹤树,是創(chuàng)建Officer對(duì)象的一個(gè)過(guò)渡者铣焊。所以利用這種模式,會(huì)有中間實(shí)例的創(chuàng)建罕伯,會(huì)加大虛擬機(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)建實(shí)例湿酸。但“default”的構(gòu)造器婿屹,可以被同package的類調(diào)用(default限制不同package類的調(diào)用)。所以推溃,我們需要將此構(gòu)造器設(shè)為private的昂利。這時(shí)就需要用到“@AllArgsConstructor(access = AccessLevel.PRIVATE)”。我們這時(shí)再看反編譯后的構(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è)類具有大量的成員變量硬萍,我們就需要提供一個(gè)全參的構(gòu)造器或大量的set方法扩所。這讓實(shí)例的創(chuàng)建和賦值,變得很麻煩朴乖,且不直觀祖屏。我們通過(guò)構(gòu)建器,可以讓變量的賦值變成鏈?zhǔn)秸{(diào)用买羞,而且調(diào)用的方法名對(duì)應(yīng)著成員變量的名稱袁勺。讓對(duì)象的創(chuàng)建和賦值都變得很簡(jiǎn)潔、直觀畜普。
5. 鏈?zhǔn)椒椒ㄙx值期丰,一定要用構(gòu)建器模式嗎?
不一定要用到構(gòu)建器模式吃挑,之所以使用構(gòu)建器模式钝荡,是因?yàn)槲覀円獎(jiǎng)?chuàng)造的對(duì)象是一個(gè)成員變量不可變的對(duì)象。
你返回去看Officer類和OfficerBuilder類舶衬,你會(huì)發(fā)現(xiàn)Officer類的成員變量都是final的埠通,而OfficerBuilder的成員變量卻沒(méi)用final修飾。因?yàn)閒inal修飾的成員變量逛犹,需要在實(shí)例創(chuàng)建時(shí)就把值確定下來(lái)植阴。但在類具有大量成員變量的時(shí)候蟹瘾,我們是不希望用戶直接調(diào)用全參構(gòu)造器的。
所以我們使用了OfficerBuilder的中間類掠手。這個(gè)類為了實(shí)現(xiàn)鏈?zhǔn)劫x值憾朴,才將變量設(shè)為非final的。無(wú)論你OfficerBuilder實(shí)例怎么賦值喷鸽,怎么改變众雷,當(dāng)你調(diào)用build方法時(shí),就會(huì)返回一個(gè)成員變量不可變的Officer實(shí)例做祝。
那如果有大量屬性砾省,但不需要它是成員變量不可變的對(duì)象,我們還需要構(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");
其實(shí)這時(shí)候構(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");
所以,我覺(jué)得你在使用lombok的"@Builder"注解的時(shí)候脯厨,還是要思考一下铅祸。當(dāng)你不需要成員變量不可變的時(shí)候,你完全沒(méi)必要使用構(gòu)建器模式合武,因?yàn)檫@會(huì)消耗java虛擬機(jī)的內(nèi)存临梗。
6. 總結(jié)
所以,我一直推薦學(xué)習(xí)知識(shí)稼跳,要在項(xiàng)目中去學(xué)習(xí)盟庞。通過(guò)項(xiàng)目,去探索項(xiàng)目以外的知識(shí)點(diǎn)岂贩,才是提升自己的快捷方法茫经。而且知識(shí)不能學(xué)死了巷波,不能網(wǎng)上有哪些知識(shí)點(diǎn)萎津,我們就只考慮這些知識(shí)點(diǎn)。我們要去思考一些別人不常想到的問(wèn)題抹镊。比如锉屈,我們?yōu)槭裁匆弥虚g類去做過(guò)渡,這么寫的目的是什么垮耳。
將上述知識(shí)吃透颈渊,面試應(yīng)對(duì)構(gòu)建器的時(shí)候遂黍,也就得心應(yīng)手了。而且通過(guò)實(shí)戰(zhàn)去回答問(wèn)題俊嗽,也更能彰顯你是個(gè)愛(ài)思考的員工雾家。
作者:永不言Qi
QQ: 591232672
e-mail:591232672@qq.com
版權(quán)聲明:轉(zhuǎn)載請(qǐng)保留此鏈接,不得用于商業(yè)用途绍豁。
雖然我不是最優(yōu)秀的程序員芯咧,但我還是想盡自己最大的努力,去分享一些學(xué)習(xí)心得竹揍。
如有錯(cuò)誤敬飒,歡迎指正。若有幸能博得您的喜愛(ài)芬位,歡迎關(guān)注及點(diǎn)贊哦无拗。
愿我們共同進(jìn)步!