詳解 Lombok 中的 @Builder 用法!
點(diǎn)擊下方“IT牧場”,選擇“設(shè)為星標(biāo)”

來源 |?cnblogs.com/ajing2018/p/14281700.html
01、詳解Lombok中的@Builder用法
Builder?使用創(chuàng)建者模式又叫建造者模式。簡單來說,就是一步步創(chuàng)建一個(gè)對象,它對用戶屏蔽了里面構(gòu)建的細(xì)節(jié),但卻可以精細(xì)地控制對象的構(gòu)造過程。
02、基礎(chǔ)使用
@Builder注釋為你的類生成相對略微復(fù)雜的構(gòu)建器API。@Builder可以讓你以下面顯示的那樣調(diào)用你的代碼,來初始化你的實(shí)例對象:
Student.builder()
???????????????.sno(?"001"?)
???????????????.sname(?"admin"?)
???????????????.sage(?18?)
???????????????.sphone(?"110"?)
???????????????.build();
@Builder可以放在類,構(gòu)造函數(shù)或方法上。雖然放在類上和放在構(gòu)造函數(shù)上這兩種模式是最常見的用例,但@Builder最容易用放在方法的用例來解釋。
03、那么@Builder內(nèi)部幫我們做了什么?
創(chuàng)建一個(gè)名為 ThisClassBuilder的內(nèi)部靜態(tài)類,并具有和實(shí)體類形同的屬性(稱為構(gòu)建器)。在構(gòu)建器中:對于目標(biāo)類中的所有的屬性和未初始化的 final字段,都會在構(gòu)建器中創(chuàng)建對應(yīng)屬性。在構(gòu)建器中:創(chuàng)建一個(gè)無參的 default構(gòu)造函數(shù)。在構(gòu)建器中:對于實(shí)體類中的每個(gè)參數(shù),都會對應(yīng)創(chuàng)建類似于 setter的方法,只不過方法名與該參數(shù)名相同。并且返回值是構(gòu)建器本身(便于鏈?zhǔn)秸{(diào)用),如上例所示。在構(gòu)建器中:一個(gè) build()方法,調(diào)用此方法,就會根據(jù)設(shè)置的值進(jìn)行創(chuàng)建實(shí)體對象。在構(gòu)建器中:同時(shí)也會生成一個(gè) toString()方法。在實(shí)體類中:會創(chuàng)建一個(gè) builder()方法,它的目的是用來創(chuàng)建構(gòu)建器。
說這么多,不如讓我們通過下面這個(gè)例子來理解
@Builder
public?class?User?{
????private?final?Integer?code?=?200;
????private?String?username;
????private?String?password;
}
?
//?編譯后:
public?class?User?{
????private?String?username;
????private?String?password;
????User(String?username,?String?password)?{
????????this.username?=?username;?this.password?=?password;
????}
????public?static?User.UserBuilder?builder()?{
????????return?new?User.UserBuilder();
????}
?
????public?static?class?UserBuilder?{
????????private?String?username;
????????private?String?password;
????????UserBuilder()?{}
?
????????public?User.UserBuilder?username(String?username)?{
????????????this.username?=?username;
????????????return?this;
????????}
????????public?User.UserBuilder?password(String?password)?{
????????????this.password?=?password;
????????????return?this;
????????}
????????public?User?build()?{
????????????return?new?User(this.username,?this.password);
????????}
????????public?String?toString()?{
????????????return?"User.UserBuilder(username="?+?this.username?+?",?password="?+?this.password?+?")";
????????}
????}
}
04、組合用法
1. @Builder中使用 @Singular 注釋集合
@Builder也可以為集合類型的參數(shù)或字段生成一種特殊的方法。它采用修改列表中一個(gè)元素而不是整個(gè)列表的方式,可以是增加一個(gè)元素,也可以是刪除一個(gè)元素。
Student.builder()
????????????????.sno(?"001"?)
????????????????.sname(?"admin"?)
????????????????.sage(?18?)
????????????????.sphone(?"110"?).sphone(?"112"?)
????????????????.build();
這樣就可以輕松地將List字段中包含2個(gè)字符串。但是想要這樣來操作集合,你需要使用@Singular來注釋字段或參數(shù)。
在使用@Singular注釋注釋一個(gè)集合字段(使用@Builder注釋類),lombok會將該構(gòu)建器節(jié)點(diǎn)視為一個(gè)集合,并生成兩個(gè)adder方法而不是setter方法。
一個(gè)向集合添加單個(gè)元素 一個(gè)將另一個(gè)集合的所有元素添加到集合中
將不生成僅設(shè)置集合(替換已添加的任何內(nèi)容)的setter。還生成了clear方法。這些singular構(gòu)建器相對而言是有些復(fù)雜的,主要是來保證以下特性:
在調(diào)用 build()時(shí),生成的集合將是不可變的。在調(diào)用 build()之后調(diào)用其中一個(gè)adder方法或clear方法不會修改任何已經(jīng)生成的對象。如果對集合修改之后,再調(diào)用build(),則會創(chuàng)建一個(gè)基于上一個(gè)對象創(chuàng)建的對象實(shí)體。生成的集合將被壓縮到最小的可行格式,同時(shí)保持高效。
@Singular只能應(yīng)用于lombok已知的集合類型。目前,支持的類型有:
java.util:
Iterable,?Collection, 和List?(一般情況下,由壓縮的不可修改的ArrayList支持).Set,?SortedSet, and?NavigableSet?(一般情況下,生成可變大小不可修改的HashSet或者TreeSet).Map,?SortedMap, and?NavigableMap?(一般情況下,生成可變大小不可修改的HashMap或者TreeMap).
Guava’s?com.google.common.collect:
ImmutableCollection?and?ImmutableListImmutableSet?and?ImmutableSortedSetImmutableMap,?ImmutableBiMap, and?ImmutableSortedMapImmutableTable
來看看使用了@Singular注解之后的編譯情況:
@Builder
public?class?User?{
????private?final?Integer?id;
????private?final?String?zipCode?=?"123456";
????private?String?username;
????private?String?password;
????@Singular
????private?List?hobbies;
}
?
//?編譯后:
public?class?User?{
????private?final?Integer?id;
????private?final?String?zipCode?=?"123456";
????private?String?username;
????private?String?password;
????private?List?hobbies;
????User(Integer?id,?String?username,?String?password,?List?hobbies)?{
????????this.id?=?id;?this.username?=?username;
????????this.password?=?password;?this.hobbies?=?hobbies;
????}
?
????public?static?User.UserBuilder?builder()?{return?new?User.UserBuilder();}
?
????public?static?class?UserBuilder?{
????????private?Integer?id;
????????private?String?username;
????????private?String?password;
????????private?ArrayList?hobbies;
????????UserBuilder()?{}
????????public?User.UserBuilder?id(Integer?id)?{?this.id?=?id;?return?this;?}
????????public?User.UserBuilder?username(String?username)?{?this.username?=?username;?return?this;?}
????????public?User.UserBuilder?password(String?password)?{?this.password?=?password;?return?this;?}
?
????????public?User.UserBuilder?hobby(String?hobby)?{
????????????if?(this.hobbies?==?null)?{
????????????????this.hobbies?=?new?ArrayList();
????????????}
????????????this.hobbies.add(hobby);
????????????return?this;
????????}
?
????????public?User.UserBuilder?hobbies(Collection?extends?String>?hobbies)?{
????????????if?(this.hobbies?==?null)?{
????????????????this.hobbies?=?new?ArrayList();
????????????}
????????????this.hobbies.addAll(hobbies);
????????????return?this;
????????}
?
????????public?User.UserBuilder?clearHobbies()?{
????????????if?(this.hobbies?!=?null)?{
????????????????this.hobbies.clear();
????????????}
????????????return?this;
????????}
?
????????public?User?build()?{
????????????List?hobbies;
????????????switch(this.hobbies?==?null???0?:?this.hobbies.size())?{
????????????case?0:
????????????????hobbies?=?Collections.emptyList();
????????????????break;
????????????case?1:
????????????????hobbies?=?Collections.singletonList(this.hobbies.get(0));
????????????????break;
????????????default:
????????????????hobbies?=?Collections.unmodifiableList(new?ArrayList(this.hobbies));
????????????}
????????????return?new?User(this.id,?this.username,?this.password,?hobbies);
????????}
????????public?String?toString()?{
????????????return?"User.UserBuilder(id="?+?this.id?+?",?username="?+?this.username?+?",?password="?+?this.password?+?",?hobbies="?+?this.hobbies?+?")";
????????}
????}
}
其實(shí),lombok的創(chuàng)作者還是很用心的,在進(jìn)行build()來創(chuàng)建實(shí)例對象時(shí), 并沒有直接使用Collections.unmodifiableList(Collection)此方法來床架實(shí)例,而是分為三種情況。
第一種,當(dāng)集合中沒有元素時(shí),創(chuàng)建一個(gè)空list 第二種情況,當(dāng)集合中存在一個(gè)元素時(shí),創(chuàng)建一個(gè)不可變的單元素list 第三種情況,根據(jù)當(dāng)前集合的元素?cái)?shù)量創(chuàng)建對應(yīng)合適大小的list
當(dāng)然我們看編譯生成的代碼,創(chuàng)建了三個(gè)關(guān)于集合操作的方法:
hobby(String hobby):向集合中添加一個(gè)元素hobbies(Collection hobbies):添加一個(gè)集合所有的元素clearHobbies():清空當(dāng)前集合數(shù)據(jù)
2. @Singular 注解配置value屬性
我們先來看看 @Singular 注解的詳情:
@Target({FIELD,?PARAMETER})
@Retention(SOURCE)
public?@interface?Singular?{
????//?修改添加集合元素的方法名
????String?value()?default?"";
}
測試如何使用注解屬性 value
@Builder
public?class?User?{
????private?final?Integer?id;
????private?final?String?zipCode?=?"123456";
????private?String?username;
????private?String?password;
????@Singular(value?=?"testHobbies")
????private?List?hobbies;
}
?
//?測試類
public?class?BuilderTest?{
????public?static?void?main(String[]?args)?{
????????User?user?=?User.builder()
????????????????.testHobbies("reading")
????????????????.testHobbies("eat")
????????????????.id(1)
????????????????.password("admin")
????????????????.username("admin")
????????????????.build();
????????System.out.println(user);
????}
}
說明,當(dāng)我們使用了注解屬性value之后,我們在使用添加集合元素時(shí)的方法名發(fā)生相應(yīng)的改變。但是,同時(shí)生成的添加整個(gè)集合的方法名發(fā)生改變了嗎?我們再來看看編譯后的代碼:
/?編譯后:
public?class?User?{
????//?省略部分代碼,只看關(guān)鍵部分
????public?static?class?UserBuilder?{
????????public?User.UserBuilder?testHobbies(String?testHobbies)?{
????????????if?(this.hobbies?==?null)?{
????????????????this.hobbies?=?new?ArrayList();
????????????}
????????????this.hobbies.add(testHobbies);
????????????return?this;
????????}
?
????????public?User.UserBuilder?hobbies(Collection?extends?String>?hobbies)?{
????????????if?(this.hobbies?==?null)?{
????????????????this.hobbies?=?new?ArrayList();
????????????}
????????????this.hobbies.addAll(hobbies);
????????????return?this;
????????}
????????
????????public?User.UserBuilder?clearHobbies()?{
????????????if?(this.hobbies?!=?null)?{
????????????????this.hobbies.clear();
????????????}
????????????return?this;
????????}
????}
}
可以看到,只有添加一個(gè)元素的方法名發(fā)生了改變。
3. @Builder.Default 的使用
比如有這樣一個(gè)實(shí)體類:
@Builder
@ToString
public?class?User?{
[email protected]
????private?final?String?id?=?UUID.randomUUID().toString();
????private?String?username;
????private?String?password;
[email protected]
????private?long?insertTime?=?System.currentTimeMillis();
}
在類中我在id和insertTime上都添加注解@Builder.Default,當(dāng)我在使用這個(gè)實(shí)體對象時(shí),我就不需要在為這兩個(gè)字段進(jìn)行初始化值,如下面這樣:
public?class?BuilderTest?{
????public?static?void?main(String[]?args)?{
????????User?user?=?User.builder()
????????????????.password("admin")
????????????????.username("admin")
????????????????.build();
????????System.out.println(user);
????}
}
?
//?輸出內(nèi)容:
User(id=416219e1-bc64-43fd-b2c3-9f8dc109c2e8,?username=admin,?password=admin,?insertTime=1546869309868)
lombok在實(shí)例化對象時(shí)就為我們初始化了這兩個(gè)字段值。
當(dāng)然,你如果再對這兩個(gè)字段進(jìn)行設(shè)值的話,那么默認(rèn)定義的值將會被覆蓋掉,如下面這樣:
public?class?BuilderTest?{
????public?static?void?main(String[]?args)?{
????????User?user?=?User.builder()
????????????????.id("admin")
????????????????.password("admin")
????????????????.username("admin")
????????????????.build();
????????System.out.println(user);
????}
}
//?輸出內(nèi)容
User(id=admin,?username=admin,?password=admin,?insertTime=1546869642151)
4. @Builder?詳細(xì)配置
下面我們再來詳細(xì)看看@Builder這個(gè)注解類地詳細(xì)實(shí)現(xiàn):
@Target({TYPE,?METHOD,?CONSTRUCTOR})
@Retention(SOURCE)
public?@interface?Builder?{
????//?如果@Builder注解在類上,可以使用[email protected]指定初始化表達(dá)式
????@Target(FIELD)
????@Retention(SOURCE)
????public?@interface?Default?{}
????//?指定實(shí)體類中創(chuàng)建?Builder?的方法的名稱,默認(rèn)為:?builder?(個(gè)人覺得沒必要修改)
????String?builderMethodName()?default?"builder";
????//?指定 Builder 中用來構(gòu)件實(shí)體類的方法的名稱,默認(rèn)為:build (個(gè)人覺得沒必要修改)
????String?buildMethodName()?default?"build";
????//?指定創(chuàng)建的建造者類的名稱,默認(rèn)為:實(shí)體類名+Builder
????String?builderClassName()?default?"";
????//?使用toBuilder可以實(shí)現(xiàn)以一個(gè)實(shí)例為基礎(chǔ)繼續(xù)創(chuàng)建一個(gè)對象。(也就是重用原來對象的值)
????boolean?toBuilder()?default?false;
????
????@Target({FIELD,?PARAMETER})
????@Retention(SOURCE)
????public?@interface?ObtainVia?{
????????//?告訴lombok使用表達(dá)式獲取值
????????String?field()?default?"";
????????//?告訴lombok使用表達(dá)式獲取值
????????String?method()?default?"";
?
????????boolean?isStatic()?default?false;
????}
}
以上注解屬性,我只測試一個(gè)比較常用的toBuilder,因?yàn)槲覀冊趯?shí)體對象進(jìn)行操作時(shí),往往會存在對某些實(shí)體對象的某個(gè)字段進(jìn)行二次賦值,這個(gè)時(shí)候就會用到這一屬性。但是,這會創(chuàng)建一個(gè)新的對象,而不是原來的對象,原來的對象屬性是不可變的,除非你自己想要給這個(gè)實(shí)體類再添加上@Data或者@setter方法。下面就來測試一下:
@Builder(toBuilder?=?true)
@ToString
public?class?User?{
????private?String?username;
????private?String?password;
}
//?測試類
public?class?BuilderTest?{
????public?static?void?main(String[]?args)?{
????????User?user1?=?User.builder()
????????????????.password("admin")
????????????????.username("admin")
????????????????.build();
????????System.out.println(user1);
?
????????User?user2?=?user1.toBuilder().username("admin2").build();
????????//?驗(yàn)證user2是否是基于user1的現(xiàn)有屬性創(chuàng)建的
????????System.out.println(user2);
????????//?驗(yàn)證對象是否是同一對象
????????System.out.println(user1?==?user2);
????}
}
//?輸出內(nèi)容
User(username=admin,?password=admin)
User(username=admin2,?password=admin)
false
5. @Builder?全局配置
#?是否禁止使用@Builder
lombok.builder.flagUsage?=?[warning?|?error]?(default:?not?set)
#?是否使用Guaua
lombok.singular.useGuava?=?[true?|?false]?(default:?false)
#?是否自動使用singular,默認(rèn)是使用
lombok.singular.auto?=?[true?|?false]?(default:?true)
總的來說 @Builder還是很好用的。
