@Builder
... 就大功告成了:輕鬆建立物件的精美 API!
@Builder
在 lombok v0.12.0 版本中作為實驗性功能推出。
自 lombok v1.16.0 版本起,@Builder
獲得了 @Singular
支援,並被提升至主要的 lombok
套件中。
自 lombok v1.16.8 版本起,帶有 @Singular
的 @Builder
新增了 clear 方法。
@Builder.Default
功能在 lombok v1.16.16 版本中新增。
從 lombok v1.18.8 版本開始,@Builder(builderMethodName = "")
是合法的(並且會抑制建構器方法的生成)。
從 lombok v1.18.8 版本開始,@Builder(access = AccessLevel.PACKAGE)
是合法的(並且將會以指定的存取層級生成建構器類別、建構器方法等等)。
概述
@Builder
註解為您的類別產生複雜的建構器 API。
@Builder
讓您自動產生所需的程式碼,使您的類別可以使用如下程式碼進行實例化:
- Person.builder()
- .name("Adam Savage")
- .city("San Francisco")
- .job("Mythbusters")
- .job("Unchained Reaction")
- .build();
@Builder
可以放在類別、建構子或方法上。雖然「在類別上」和「在建構子上」模式是最常見的用例,但使用「方法」用例最容易解釋 @Builder
。
使用 @Builder
註解的方法(從現在起稱為目標)會導致生成以下 7 個項目:
- 一個名為
FooBuilder
的內部靜態類別,具有與靜態方法相同的型別引數(稱為建構器)。 - 在建構器中:目標的每個參數都有一個私有的非靜態非 final 欄位。
- 在建構器中:一個套件私有的無引數空建構子。
- 在建構器中:目標的每個參數都有一個類似 'setter' 的方法:它具有與該參數相同的型別和名稱。它返回建構器本身,以便可以鏈式調用 setter,如上面的範例所示。
- 在建構器中:一個
build()
方法,它調用該方法,並傳入每個欄位。它返回與目標返回相同的型別。 - 在建構器中:一個合理的
toString()
實作。 - 在包含目標的類別中:一個
builder()
方法,它建立建構器的新實例。
@EqualsAndHashCode
。
@Builder
可以為集合參數/欄位生成所謂的「單數」方法。這些方法接受 1 個元素而不是整個列表,並將該元素新增到列表中。例如:
- Person.builder()
- .job("Mythbusters")
- .job("Unchained Reaction")
- .build();
將導致 List<String> jobs
欄位包含 2 個字串。要獲得此行為,欄位/參數需要使用 @Singular
註解。此功能有 其自己的文件。
既然「方法」模式已經清楚,那麼在建構子上放置 @Builder
註解的功能也類似;實際上,建構子只是具有特殊語法來調用它們的靜態方法:它們的「返回型別」是它們建構的類別,並且它們的型別參數與類別本身的型別參數相同。
最後,將 @Builder
應用於類別,就好像您將 @AllArgsConstructor(access = AccessLevel.PACKAGE)
新增到類別,並將 @Builder
註解應用於此全引數建構子。這僅在您自己沒有編寫任何顯式建構子,或允許 lombok 創建一個建構子(例如使用 @NoArgsConstructor
)的情況下才有效。如果您確實有顯式建構子,請將 @Builder
註解放在建構子上,而不是放在類別上。請注意,如果您在類別上同時放置 `@Value` 和 `@Builder`,則 `@Builder` 想要生成的套件私有建構子將「勝出」,並抑制 `@Value` 想要建立的建構子。
如果使用 @Builder
生成建構器以產生您自己類別的實例(除非將 @Builder
新增到不返回您自己型別的方法,否則始終是這種情況),您可以使用 @Builder(toBuilder = true)
以在您的類別中也生成一個名為 toBuilder()
的實例方法;它建立一個新的建構器,該建構器從此實例的所有值開始。您可以將 @Builder.ObtainVia
註解放在參數上(如果是建構子或方法)或欄位上(如果是類型上的 @Builder
),以指示從此實例中取得該欄位/參數值的替代方法。例如,您可以指定要調用的方法:@Builder.ObtainVia(method = "calculateFoo")
。
建構器類別的名稱為 FoobarBuilder
,其中 Foobar 是目標返回型別的簡化、標題大小寫形式 - 也就是說,對於建構子和類型上的 @Builder
,是您的型別名稱,而對於方法上的 @Builder
,則是返回型別的名稱。例如,如果將 @Builder
應用於名為 com.yoyodyne.FancyList<T>
的類別,則建構器名稱將為 FancyListBuilder<T>
。如果將 @Builder
應用於返回 void
的方法,則建構器將被命名為 VoidBuilder
。
建構器的可配置方面包括:
- 建構器的類別名稱(預設值:返回型別 + 'Builder')
build()
方法的名稱(預設值:"build"
)builder()
方法的名稱(預設值:"builder"
)- 是否需要
toBuilder()
(預設值:否) - 所有生成的元素的存取層級(預設值:public)。
- (不建議)如果您希望建構器的 'set' 方法具有前綴,例如
Person.builder().setName("Jane").build()
而不是Person.builder().name("Jane").build()
,以及它應該是什麼。
@Builder(builderClassName = "HelloWorldBuilder", buildMethodName = "execute", builderMethodName = "helloWorld", toBuilder = true, access = AccessLevel.PRIVATE, setterPrefix = "set")
想要將您的建構器與 Jackson(JSON/XML 工具)一起使用嗎?我們已為您準備好:請查看 @Jacksonized 功能。
@Builder.Default
如果在建構階段中從未設定某個欄位/參數,則它始終會獲得 0 / null
/ false。如果您已將 @Builder
放在類別上(而不是方法或建構子上),則可以改為直接在欄位上指定預設值,並使用 @Builder.Default
註解欄位。
@Builder.Default private final long created = System.currentTimeMillis();
調用 Lombok 生成的建構子(例如 @NoArgsConstructor
)也將使用使用 @Builder.Default
指定的預設值,但是顯式建構子將不再使用預設值,並且需要手動設定或調用 Lombok 生成的建構子(例如 this();
)來設定預設值。
@Singular
通過使用 @Singular
註解註解參數之一(如果使用 @Builder
註解方法或建構子)或欄位(如果使用 @Builder
註解類別),lombok 將把該建構器節點視為集合,並且它會生成 2 個 'adder' 方法而不是 'setter' 方法。一個將單個元素新增到集合中,另一個將另一個集合的所有元素新增到集合中。不會生成僅設定集合的 setter(替換已新增的任何內容)。還會生成 'clear' 方法。這些 'singular' 建構器非常複雜,目的是為了保證以下屬性:
- 調用
build()
時,生成的集合將是不可變的。 - 在調用
build()
後調用 'adder' 方法之一或 'clear' 方法,不會修改任何已生成的物件,並且,如果稍後再次調用build()
,則會生成另一個包含自建構器建立以來新增的所有元素的集合。 - 生成的集合將被壓縮為最小的可行格式,同時保持高效。
@Singular
只能應用於 lombok 已知的集合類型。目前,支援的類型包括:
-
java.util
:-
Iterable
、Collection
和List
(在一般情況下由壓縮的不可修改的ArrayList
支援)。 -
Set
、SortedSet
和NavigableSet
(在一般情況下由智慧調整大小的不可修改的HashSet
或TreeSet
支援)。 -
Map
、SortedMap
和NavigableMap
(在一般情況下由智慧調整大小的不可修改的HashMap
或TreeMap
支援)。
-
-
Guava 的
com.google.common.collect
-
ImmutableCollection
和ImmutableList
(由ImmutableList
的建構器功能支援)。 -
ImmutableSet
和ImmutableSortedSet
(由這些類型的建構器功能支援)。 -
ImmutableMap
、ImmutableBiMap
和ImmutableSortedMap
(由這些類型的建構器功能支援)。 -
ImmutableTable
(由ImmutableTable
的建構器功能支援)。
-
如果您的標識符以常用英語編寫,lombok 會假定任何帶有 @Singular
的集合的名稱都是英語複數,並將嘗試自動將該名稱單數化。如果可能,add-one 方法將使用此名稱。例如,如果您的集合稱為 statuses
,則 add-one 方法將自動稱為 status
。您也可以通過將單數形式作為引數傳遞給註解來顯式指定標識符的單數形式,如下所示:@Singular("axis") List<Line> axes;
。
如果 lombok 無法將您的標識符單數化,或者它不明確,lombok 將生成錯誤並強制您顯式指定單數名稱。
下面的程式碼片段未顯示 lombok 為 @Singular
欄位/參數生成的內容,因為它相當複雜。您可以在 此處 查看程式碼片段。
如果還使用 setterPrefix = "with"
,則生成的名稱例如為 withName
(新增 1 個名稱)、withNames
(新增多個名稱)和 clearNames
(重置所有名稱)。
通常,生成的「複數形式」方法(它接收一個集合,並將此集合中的每個元素新增進去)將檢查是否傳遞了 null
,方式與 @NonNull
相同(預設情況下,會拋出帶有適當訊息的 NullPointerException
)。但是,您也可以告訴 lombok 忽略此類集合(因此,不新增任何內容,立即返回):@Singular(ignoreNullCollections = true
。
使用 Jackson
您可以自訂建構器的某些部分,例如通過自行建立建構器類別,將另一個方法新增到建構器類別,或註解建構器類別中的方法。Lombok 將生成您未手動新增的所有內容,並將其放入此建構器類別中。例如,如果您嘗試配置 jackson 以對集合使用特定的子類型,則可以編寫如下內容:
@Value @Builder @JsonDeserialize(builder = JacksonExample.JacksonExampleBuilder.class) public class JacksonExample { @Singular(nullBehavior = NullCollectionBehavior.IGNORE) private List<Foo> foos; @JsonPOJOBuilder(withPrefix = "") public static class JacksonExampleBuilder implements JacksonExampleBuilderMeta { } private interface JacksonExampleBuilderMeta { @JsonDeserialize(contentAs = FooImpl.class) JacksonExampleBuilder foos(List<? extends Foo> foos) } }
使用 Lombok
import lombok.Builder;
|
Vanilla Java
import java.util.Set;
|
支援的配置鍵
-
lombok.builder.className
= [一個 java 標識符,帶有一個可選的星號來指示返回型別名稱的位置](預設值:*Builder
) - 除非您使用
builderClassName
參數顯式選擇建構器的類別名稱,否則將選擇此名稱;名稱中的任何星號都會被替換為相關的返回型別。 -
lombok.builder.flagUsage
= [`warning` | `error`](預設值:未設定) - 如果已配置,Lombok 將把
@Builder
的任何用法標記為警告或錯誤。 -
lombok.singular.useGuava
= [`true` | `false`](預設值:false) - 如果為
true
,lombok 將使用 guava 的ImmutableXxx
建構器和類型來實作java.util
集合介面,而不是建立基於Collections.unmodifiableXxx
的實作。如果您使用此設定,則必須確保 guava 實際上在 classpath 和 buildpath 上可用。如果您的欄位/參數具有 guavaImmutableXxx
類型之一,則會自動使用 Guava。 -
lombok.singular.auto
= [`true` | `false`](預設值:true) - 如果為
true
(這是預設值),lombok 會自動嘗試將您的標識符名稱單數化,方法是假定它是常用的英語複數。如果為false
,您必須始終顯式指定單數名稱,並且如果您不這樣做,lombok 將生成錯誤(如果您使用英語以外的語言編寫程式碼,則很有用)。
小字說明
對於 java.util.NavigableMap/Set
的 @Singular
支援僅在您使用 JDK1.8 或更高版本編譯時才有效。
您無法手動提供 @Singular
節點的部分或全部;lombok 生成的程式碼對此來說太複雜了。如果您想手動控制與某些欄位或參數關聯的建構器程式碼(部分),請不要使用 @Singular
,並手動新增您需要的一切。
排序的集合(java.util:SortedSet
、NavigableSet
、SortedMap
、NavigableMap
和 guava:ImmutableSortedSet
、ImmutableSortedMap
)要求集合的型別引數具有自然順序(實作 java.util.Comparable
)。無法傳遞顯式的 Comparator
以在建構器中使用。
如果目標集合來自 java.util
套件,則即使集合是 set 或 map,ArrayList
也會用於儲存作為 @Singular
標記欄位的調用方法新增的元素。因為 lombok 確保生成的集合是壓縮的,因此無論如何都必須建構 set 或 map 的新後備實例,並且在建構過程中將資料儲存為 ArrayList
比將其儲存為 map 或 set 更有效率。此行為在外部不可見,是當前 @Singular @Builder
的 java.util
配方實作的實作細節。
將 toBuilder = true
應用於方法時,註解方法的任何型別參數也必須出現在返回型別中。
@Builder.Default
欄位上的初始化器會被移除並儲存在靜態方法中,以保證如果在建構中指定了值,則此初始化器根本不會執行。這確實意味著初始化器不能引用 this
、super
或任何非靜態成員。如果 lombok 為您生成了建構子,它也會使用初始化器初始化此欄位。
建構器中生成的欄位,用於表示設定了 @Builder.Default
的欄位,稱為 propertyName$value
;還會生成一個額外的布林欄位 propertyName$set
,以追蹤它是否已設定。這是一個實作細節;請勿編寫與這些欄位交互的程式碼。相反,如果您想在建構器內部的自訂方法中設定屬性,請調用生成的建構器-setter 方法。
關於 null 值的各種知名註解會導致插入 null 檢查,並將複製到建構器的 'setter' 方法的參數。有關更多資訊,請參閱 Getter/Setter 文件的小字說明。
您可以抑制 builder()
方法的生成,例如因為您只想使用 toBuilder()
功能,方法是使用:@Builder(builderMethodName = "")
。當您這樣做時,關於缺少 @Builder.Default
註解的任何警告都將消失,因為當僅使用 toBuilder()
建立建構器實例時,此類警告是不相關的。
您可以使用 @Builder
進行複製建構子:foo.toBuilder().build()
進行淺層複製。如果您只想使用此功能,請考慮抑制 builder
方法的生成,方法是使用:@Builder(toBuilder = true, builderMethodName = "")
。
由於 javac 處理靜態匯入的特殊方式,嘗試對靜態 builder()
方法執行非星號靜態匯入將不起作用。可以使用星號靜態匯入:import static TypeThatHasABuilder.*;
,或者不要靜態匯入 builder
方法。
如果將存取層級設定為 PROTECTED
,則建構器類別內部生成的所有方法實際上都會生成為 public
;protected
關鍵字在內部類別中的含義不同,並且 PROTECTED
將指示的精確行為(允許同一個套件中的任何來源存取,以及來自*標有 @Builder
的外部類別*的任何子類別)是不可能的,並且將內部成員標記為 public
是我們能做到的最接近的方式。
如果您已通過 lombok.config
鍵 lombok.addNullAnnotations
配置了 null 值註解風格,則針對 @Singular
標記屬性生成的任何複數形式建構器方法(這些複數形式方法採用某種集合並新增所有元素)都會在參數上獲得 null 值註解。您通常會獲得一個非 null 值註解,但是,如果您已將在作為集合傳入的 null
上的行為配置為 IGNORE
,則會改為生成可為 null 的註解。