@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 = "")
是合法的(并将阻止生成 builder 方法)。
自 lombok v1.18.8 版本起,@Builder(access = AccessLevel.PACKAGE)
是合法的(并将使用指定的访问级别生成 builder 类、builder 方法等)。
概述
@Builder
注解为您的类生成复杂的 builder API。
@Builder
让您自动生成所需的代码,以便您的类可以通过如下代码实例化
- Person.builder()
- .name("Adam Savage")
- .city("San Francisco")
- .job("Mythbusters")
- .job("Unchained Reaction")
- .build();
@Builder
可以放置在类、构造函数或方法上。虽然“在类上”和“在构造函数上”模式是最常见的用例,但使用“方法”用例最容易解释 @Builder
。
使用 @Builder
注解的方法(以下称为目标)会导致生成以下 7 项内容
- 一个名为
FooBuilder
的内部静态类,它具有与静态方法相同的类型参数(称为 builder)。 - 在 builder 中:目标 的每个参数对应一个私有的、非静态的、非 final 的字段。
- 在 builder 中:一个包私有的无参数空构造函数。
- 在 builder 中:目标 的每个参数对应一个类似 'setter' 的方法:它具有与该参数相同的类型和相同的名称。它返回 builder 本身,以便 setter 调用可以像上面的示例中那样链式调用。
- 在 builder 中:一个
build()
方法,它调用目标方法,并传入每个字段。它返回与目标返回类型相同的类型。 - 在 builder 中:一个合理的
toString()
实现。 - 在包含目标的类中:一个
builder()
方法,它创建一个新的 builder 实例。
@EqualsAndHashCode
。
@Builder
可以为集合参数/字段生成所谓的“singular”方法。 这些方法接受 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
添加到不返回您自己类型的方法,否则始终是这种情况),您可以使用 @Builder(toBuilder = true)
也在您的类中生成一个名为 toBuilder()
的实例方法; 它创建一个新的 builder,该 builder 从此实例的所有值开始。 您可以将 @Builder.ObtainVia
注解放在参数(在构造函数或方法的情况下)或字段(在 @Builder
在类型上的情况下)上,以指示从此实例获取该字段/参数的值的替代方法。 例如,您可以指定要调用的方法:@Builder.ObtainVia(method = "calculateFoo")
。
builder 类的名称为 FoobarBuilder
,其中 Foobar 是目标返回类型的简化标题形式 - 即,在构造函数和类型上使用 @Builder
时,它是您的类型名称,而在方法上使用 @Builder
时,它是返回类型名称。 例如,如果 @Builder
应用于名为 com.yoyodyne.FancyList<T>
的类,则 builder 名称将为 FancyListBuilder<T>
。 如果 @Builder
应用于返回 void
的方法,则 builder 将命名为 VoidBuilder
。
builder 的可配置方面包括
- builder 的类名(默认值:返回类型 + 'Builder')
- build() 方法的名称(默认值:
"build"
) - builder() 方法的名称(默认值:
"builder"
) - 是否需要
toBuilder()
(默认值:否) - 所有生成元素的访问级别(默认值:
public
)。 - (不推荐)如果您希望 builder 的 '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")
希望将您的 builder 与 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 会将该 builder 节点视为集合,并生成 2 个“adder”方法而不是“setter”方法。 一个方法将单个元素添加到集合中,另一个方法将另一个集合的所有元素添加到集合中。 不会生成仅设置集合的 setter(替换已添加的任何内容)。 还会生成一个“clear”方法。 这些“singular”builder 非常复杂,以保证以下属性
- 当调用
build()
时,生成的集合将是不可变的。 - 在调用
build()
之后调用“adder”方法之一或“clear”方法不会修改任何已生成的对象,并且,如果稍后再次调用build()
,则会生成另一个包含自 builder 创建以来添加的所有元素的集合。 - 生成的集合将被压缩为最小的可行格式,同时保持高效。
@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
的 builder 功能支持)。 -
ImmutableSet
和ImmutableSortedSet
(由这些类型的 builder 功能支持)。 -
ImmutableMap
、ImmutableBiMap
和ImmutableSortedMap
(由这些类型的 builder 功能支持)。 -
ImmutableTable
(由ImmutableTable
的 builder 功能支持)。
-
如果您的标识符是用常用英语编写的,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
您可以通过自己创建 builder 类来自定义 builder 的某些部分,例如向 builder 类添加另一个方法,或注解 builder 类中的方法。 Lombok 将生成您未手动添加的所有内容,并将其放入此 builder 类中。 例如,如果您尝试配置 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;
|
原生 Java
import java.util.Set;
|
支持的配置键
-
lombok.builder.className
= [一个 java 标识符,带有一个可选的星号,指示返回类型名称的位置](默认值:*Builder
) - 除非您使用
builderClassName
参数显式选择 builder 的类名,否则将选择此名称; 名称中的任何星号都将替换为相关的返回类型。 -
lombok.builder.flagUsage
= [warning
|error
](默认值:未设置) - 如果配置了,Lombok 将把任何
@Builder
的使用标记为警告或错误。 -
lombok.singular.useGuava
= [true
|false
](默认值:false) - 如果为
true
,lombok 将使用 guava 的ImmutableXxx
builder 和类型来实现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 生成的代码对此来说太复杂了。 如果您想手动控制与某些字段或参数关联的 builder 代码(的一部分),请不要使用 @Singular
并手动添加您需要的一切。
排序集合(java.util:SortedSet
、NavigableSet
、SortedMap
、NavigableMap
和 guava:ImmutableSortedSet
、ImmutableSortedMap
)要求集合的类型参数具有自然顺序(实现 java.util.Comparable
)。 无法传递显式的 Comparator
以在 builder 中使用。
如果目标集合来自 java.util
包,则即使集合是 set 或 map,ArrayList
也用于在 @Singular
标记字段的调用方法中存储添加的元素。 因为 lombok 确保生成的集合是压缩的,所以无论如何都必须构造 set 或 map 的新后备实例,并且在构建过程中将数据存储为 ArrayList
比将其存储为 map 或 set 更有效。 此行为在外部不可见,是当前 java.util
中 @Singular @Builder
配方的实现的实现细节。
对于应用于方法的 toBuilder = true
,注解方法的任何类型参数本身也必须出现在返回类型中。
@Builder.Default
字段上的初始化器被删除并存储在静态方法中,以保证如果在构建中指定了值,则根本不会执行此初始化器。 这确实意味着初始化器不能引用 this
、super
或任何非静态成员。 如果 lombok 为您生成构造函数,它也会使用初始化器初始化此字段。
builder 中生成的字段,用于表示设置了 @Builder.Default
的字段,称为 propertyName$value
; 还会生成一个名为 propertyName$set
的附加布尔字段,以跟踪它是否已设置。 这是一个实现细节; 不要编写与这些字段交互的代码。 而是调用生成的 builder-setter 方法(如果您想在 builder 内的自定义方法中设置属性)。
关于空值的各种众所周知的注解会导致插入空值检查,并将复制到 builder 的“setter”方法的参数。 有关更多信息,请参见 Getter/Setter 文档的小字号声明。
您可以禁止生成 builder()
方法,例如,因为您只是想要 toBuilder()
功能,方法是使用:@Builder(builderMethodName = "")
。 当您这样做时,关于缺少 @Builder.Default
注解的任何警告都将消失,因为当仅使用 toBuilder()
来创建 builder 实例时,此类警告是不相关的。
您可以将 @Builder
用于复制构造函数:foo.toBuilder().build()
进行浅克隆。 如果您只是想要此功能,请考虑禁止生成 builder
方法,方法是使用:@Builder(toBuilder = true, builderMethodName = "")
。
由于 javac 处理静态导入的特殊方式,尝试对静态 builder()
方法执行非星号静态导入将不起作用。 可以使用星号静态导入:`import static TypeThatHasABuilder.*;` 或者不要静态导入 builder
方法。
如果将访问级别设置为 PROTECTED
,则 builder 类内部生成的所有方法实际上都生成为 public
; protected
关键字在内部类中的含义不同,并且 PROTECTED
将指示的精确行为(允许同一包中的任何源访问,以及来自外部类(标记为 @Builder
)的任何子类)是不可能的,并且将内部成员标记为 public
是我们能做到的最接近的。
如果您已通过 lombok.config
键 lombok.addNullAnnotations
配置了空值注解风格,则为 @Singular
标记的属性生成的任何复数形式的 builder 方法(这些复数形式的方法接受某种集合并添加所有元素)都会在参数上获得一个空值注解。 通常您会获得一个非空值注解,但是如果您已将传递到 IGNORE
的集合中的 null
上的行为配置为 IGNORE
,则会改为生成一个可为空的注解。