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)