Lombok之@Builder註解
Lombok之@Builder註解
前言
Lombok
大家都知道,在使用POJO
過程中,它給我們帶來了很多便利,省下大量寫get
、set
方法、構造器、equal
、toString
方法的時間。除此之外,通過@Builder
註解,lombok
還可以方便的實現建造者模式。
認識@Builder註解
lombok
註解在java
進行編譯時進行程式碼的構建,對於java
物件的建立工作它可以更優雅,不需要寫多餘的重複的程式碼,這對於JAVA
開發人員是很重要的,在出現lombok
之後,物件的建立工作更提供Builder
方法,它提供在設計資料實體時,對外保持private setter
,而對屬性的賦值採用Builder
@Builder
宣告實體,表示可以進行Builder
方式初始化,@Value
註解,表示只公開getter
,對所有屬性的setter
都封閉,即private
修飾,所以它不能和@Builder
現起用
簡單使用
在專案生成的實體類上,只需要我們新增@Builder
註解即可。示例程式碼:
package com.zy.pagehelper.model; import lombok.Builder; import lombok.Data; import java.io.Serializable; @Data @Builder public class Admin implements Serializable { private Long id; private String department; private String email; private String encodedpassword; private String name; private String username; private static final long serialVersionUID = 1L; }
專案中使用。程式碼例項:
Admin admins = Admin.builder() .id(admin.getId()) .name(admin.getName()) .email(admin.getEmail()) .department(admin.getDepartment()) .username(admin.getUsername()) .build();
根據上面的示例,我們可以對@Builder註解有一個簡單的認識。當我們向一個物件賦值的時候,可以通過@Builder
註解類似於鏈式的呼叫物件進行賦值。它的主要優點就是可以優雅的給物件賦值,修改物件,省去了set方法來定義屬性內容。
深入探究--原理
如果對建造者設計模式不太清楚的,可以先了解一下:建造者模式
@Builder
註釋為你的類生成相對略微複雜的構建器API
。@Builder
可以讓你以下面顯示的那樣類似於鏈式的呼叫你的程式碼,來初始化你的例項物件:
Admin admins = Admin.builder()
.id(admin.getId())
.name(admin.getName())
.email(admin.getEmail())
.department(admin.getDepartment())
.username(admin.getUsername())
.build();
@Builder
可以放在類,構造器或方法上。雖然“基於類”和“基於構造器”模式是最常見的用例,但使用“方法”用例最容易解釋。
被@Builder
註解的方法(從現在開始稱為target
)將生成以下7件事:
- 一個內部靜態類,名為FooBuilder,其型別引數與靜態方法相同(稱為builder)
- 在構建器中:目標的每個引數有一個私有的非靜態非最終欄位
- 在builder中:包私有的無引數空建構函式
- 在builder中:對目標的每個引數使用類似於“ setter”的方法:與該引數具有相同的型別和相同的名稱。如上例所示,它返回構建器本身,以便可以將setter呼叫連結起來
- 在builder中:build()呼叫該方法的方法,並在每個欄位中傳遞。它返回與目標返回相同的型別
- 有意義的toString()實現
- 在包含target的類中:一個builder()方法,該方法建立builder的新例項
下面我們通過class
類,與我們上面的每一條進行對比:
@Builder
public class Card {
private int id;
private String name;
private boolean sex;
}
使用@Builder
註解反編譯後的class
類:
public class Card {
private int id;
private String name;
private boolean sex;
Card(int id, String name, boolean sex) {
this.id = id;
this.name = name;
this.sex = sex;
}
//註解在編譯後使得Card類中多了一個名為Card.CardBuilder的靜態內部類
public static Card.CardBuilder builder() {
return new Card.CardBuilder();
}
public static class CardBuilder {
private int id;
private String name;
private boolean sex;
CardBuilder() {
}
//通過靜態內部類,實現了name、sex、id等的屬性方法
//其實這些方法和setAttribute十分類似,只是額外返回了例項本身,這使得它可以使用類似於鏈式呼叫的寫法。
public Card.CardBuilder id(int id) {
this.id = id;
return this;
}
public Card.CardBuilder name(String name) {
this.name = name;
return this;
}
public Card.CardBuilder sex(boolean sex) {
this.sex = sex;
return this;
}
//build方法呼叫Card類的全參構造方法來生成Card例項
//Card類還是實現了builder方法,這個方法生成一個空的Card.CardBuilder例項。
public Card build() {
return new Card(this.id, this.name, this.sex);
}
public String toString() {
return "Card.CardBuilder(id=" + this.id + ", name=" + this.name + ", sex=" + this.sex + ")";
}
}
}
使用@Builder註解有無繼承
一、 無繼承父類的情況
可以將@Builder
註解直接放置在類上,示例程式碼:
@Data
@Builder
public class Student {
private String schoolName;
private String grade;
public static void main(String[] args) {
Student student = Student.builder().schoolName("清華附小").grade("二年級").build();
// Student(schoolName=清華附小, grade=二年級)
System.out.println(student);
}
}
二、有繼承父類的情況
- 對於父類,使用
@AllArgsConstructor
註解- 對於子類,手動編寫全引數構造器,內部呼叫父類全引數構造器,在子類全引數構造器上使用
@Builder
註解
通過這種方式,子類Builder
物件可以使用父類的所有私有屬性。但是這種解法也有兩個副作用:
- 因為使用
@AllArgsConstructor
註解,父類建構函式欄位的順序由宣告欄位的順序決定,如果子類建構函式傳參的時候順序不一致,欄位型別還一樣的話,出了錯不好發現- 如果父類欄位有增減,所有子類的構造器都要修改
示例程式碼父類:
@Data
// 對於父類,使用@AllArgsConstructor註解
@AllArgsConstructor
public class Person {
private int weight;
private int height;
}
示例程式碼子類:
@Data
@ToString(callSuper = true)
public class Student extends Person {
private String schoolName;
private String grade;
// 對於子類,手動編寫全引數構造器,內部呼叫父類全引數構造器,在子類全引數構造器上使用@Builder註解
@Builder
public Student(int weight, int height, String schoolName, String grade) {
super(weight, height);
this.schoolName = schoolName;
this.grade = grade;
}
public static void main(String[] args) {
Student student = Student.builder().schoolName("清華附小").grade("二年級")
.weight(10).height(10).build();
// Student(super=Person(weight=10, height=10), schoolName=清華附小, grade=二年級)
System.out.println(student.toString());
}
}
@Builder註解導致預設值無效問題
@Builder註解導致預設值無效---解決方案
看完上面的內容我們知道@Builder
註解它可以讓我們很方便的使用builder模式構建物件。但是當我們給物件賦有預設值的時候會被@Builder
註解清除掉。
從下面一段程式碼中,我們可以更加清楚的認識到這一點:
public class BuilderTest {
@lombok.Builder
@lombok.Data
private static class Builder {
private String name = "1232";
}
@Test
public void test() {
Builder builder = Builder.builder().build();
System.out.println(builder.getName());
}
}
---列印結果---
null
那麼不想讓這個預設值被清除,就只能用另外一個註解來對屬性進行設定:@lombok.Builder.Default
public class BuilderTest {
@lombok.Builder
@lombok.Data
private static class Builder {
@lombok.Builder.Default
private String name = "1232";
}
@Test
public void test() {
Builder builder = Builder.builder().build();
System.out.println(builder.getName());
}
}
---列印結果---
1232
- 需要注意的是
@lombok.Builder.Default
這個註解是後來才有的,目前已知的是1.2.X沒有,1.6.X中有這個註解。
@Builder註解導致預設值無效----分析原因
由上面文章內容,我們可以知道,當我們使用
@Builder
註解時,編譯後會生成一個靜態內部類,通過靜態內部類,最終才實現屬性的方法,但是現實的方法,並沒有預設值,這就導致當我們builder之後的實體類的屬性值是null。
示例程式碼:
//編譯前
@lombok.Builder
class Example {
private String name = "123";
}
//編譯後class類
class Example {
private String name;
private Example(String name) {
this.name = name;
}
public static ExampleBuilder builder() {
return new ExampleBuilder();
}
public static class ExampleBuilder {
private String name;
private ExampleBuilder() {}
//這裡我們可以看到,靜態內部類實現了屬性方法,但是並沒有對預設值做處理,
//所有builder之後返回的屬性值為null
public ExampleBuilder name(String name) {
this.name = name;
return this;
}
@java.lang.Override public String toString() {
return "Example(name = " + name + ")";
}
public Example build() {
return new Example(name);
}
}
}
推薦參考blog:@Builder註解構造器生成的詳解
@Builder相關注解
@Builder.Default 使用
比如有這樣一個實體類:
@Builder
@ToString
public class User {
@Builder.Default
private final String id = UUID.randomUUID().toString();
private String username;
private String password;
@Builder.Default
private long insertTime = System.currentTimeMillis();
}
在類中我在id
和insertTime
上都添加註解@Builder.Default
,當我在使用這個實體物件時,我就不需要在為這兩個欄位進行初始化值,如下面這樣:
public class BuilderTest {
public static void main(String[] args) {
User user = User.builder()
.password("jdkong")
.username("jdkong")
.build();
System.out.println(user);
}
}
// 輸出內容:
User(id=416219e1-bc64-43fd-b2c3-9f8dc109c2e8, username=jdkong, password=jdkong, insertTime=1546869309868)
lombok
在例項化物件時就為我們初始化了這兩個欄位值。
當然,你如果再對這兩個欄位進行設值的話,那麼預設定義的值將會被覆蓋掉,如下面這樣:
public class BuilderTest {
public static void main(String[] args) {
User user = User.builder()
.id("jdkong")
.password("jdkong")
.username("jdkong")
.build();
System.out.println(user);
}
}
// 輸出內容
User(id=jdkong, username=jdkong, password=jdkong, insertTime=1546869642151)
@Builder 詳細配置
@Target({TYPE, METHOD, CONSTRUCTOR})
@Retention(SOURCE)
public @interface Builder {
// 如果@Builder註解在類上,可以使用 @Builder.Default指定初始化表示式
@Target(FIELD)
@Retention(SOURCE)
public @interface Default {}
// 指定實體類中建立 Builder 的方法的名稱,預設為: builder (個人覺得沒必要修改)
String builderMethodName() default "builder";
// 指定 Builder 中用來構件實體類的方法的名稱,預設為:build (個人覺得沒必要修改)
String buildMethodName() default "build";
// 指定建立的建造者類的名稱,預設為:實體類名+Builder
String builderClassName() default "";
// 使用toBuilder可以實現以一個例項為基礎繼續建立一個物件。(也就是重用原來物件的值)
boolean toBuilder() default false;
@Target({FIELD, PARAMETER})
@Retention(SOURCE)
public @interface ObtainVia {
// 告訴lombok使用表示式獲取值
String field() default "";
// 告訴lombok使用表示式獲取值
String method() default "";
boolean isStatic() default false;
}
}
@Builder 全域性配置
# 是否禁止使用@Builder
lombok.builder.flagUsage = [warning | error] (default: not set)
#是否使用Guaua
lombok.singular.useGuava = [true | false] (default: false)
# 是否自動使用singular,預設是使用
lombok.singular.auto = [true | false] (default: true)