Lombok之@Builder注解


Lombok之@Builder注解

前言

Lombok大家都知道,在使用POJO過程中,它給我們帶來了很多便利,省下大量寫getset方法、構造器、equaltoString方法的時間。除此之外,通過@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件事:

  1. 一個內部靜態類,名為FooBuilder,其類型參數與靜態方法相同(稱為builder)
  2. 在構建器中:目標的每個參數有一個私有的非靜態非最終字段
  3. 在builder中:包私有的無參數空構造函數
  4. 在builder中:對目標的每個參數使用類似於“ setter”的方法:與該參數具有相同的類型和相同的名稱。如上例所示,它返回構建器本身,以便可以將setter調用鏈接起來
  5. 在builder中:build()調用該方法的方法,並在每個字段中傳遞。它返回與目標返回相同的類型
  6. 有意義的toString()實現
  7. 在包含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);
    }
}

二、有繼承父類的情況

  1. 對於父類,使用@AllArgsConstructor注解
  2. 對於子類,手動編寫全參數構造器,內部調用父類全參數構造器,在子類全參數構造器上使用@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();
}

在類中我在idinsertTime上都添加注解@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)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM