如何从 Lombok 构建器中排除 属性?
How to exclude property from Lombok builder?
我有一个名为 "XYZClientWrapper" 的 class ,它具有以下结构:
@Builder
XYZClientWrapper{
String name;
String domain;
XYZClient client;
}
我想要的 属性 XYZClient client
没有生成构建函数
Lombok 是否支持这样的用例?
是的,您可以将 @Builder 放在构造函数或静态(工厂)方法上,只包含您想要的字段。
披露:我是 Lombok 开发人员。
或者,我发现将字段标记为 final、static 或 static final 指示 @Builder
忽略此字段。
@Builder
public class MyClass {
private String myField;
private final String excludeThisField = "bar";
}
龙目岛 1.16.10
在代码中创建构建器并为您的 属性 添加私有 setter。
@Builder
XYZClientWrapper{
String name;
String domain;
XYZClient client;
public static class XYZClientWrapperBuilder {
private XYZClientWrapperBuilder client(XYZClient client) { return this; }
}
}
我发现我可以实现一个"shell"的static Builder class,添加我想用私有访问修饰符隐藏的方法,在建设者。同样,我也可以向构建器添加自定义方法。
package com.something;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import java.time.ZonedDateTime;
@Data
@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class MyClass{
//The builder will generate a method for this property for us.
private String anotherProperty;
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "localDateTime", column = @Column(name = "some_date_local_date_time")),
@AttributeOverride(name = "zoneId", column = @Column(name = "some__date_zone_id"))
})
@Getter(AccessLevel.PRIVATE)
@Setter(AccessLevel.PRIVATE)
private ZonedDateTimeEmbeddable someDateInternal;
public ZonedDateTime getSomeDate() {
return someDateInternal.toZonedDateTime();
}
public void setSomeDate(ZonedDateTime someDate) {
someDateInternal = new ZonedDateTimeEmbeddable(someDate);
}
public static class MyClassBuilder {
//Prevent direct access to the internal private field by pre-creating builder method with private access.
private MyClassBuilder shipmentDateInternal(ZonedDateTimeEmbeddable zonedDateTimeEmbeddable) {
return this;
}
//Add a builder method because we don't have a field for this Type
public MyClassBuilder someDate(ZonedDateTime someDate) {
someDateInternal = new ZonedDateTimeEmbeddable(someDate);
return this;
}
}
}
这是我的首选解决方案。有了它,您可以在最后创建您的字段 client
并根据先前由构建器设置的其他字段。
XYZClientWrapper{
String name;
String domain;
XYZClient client;
@Builder
public XYZClientWrapper(String name, String domain) {
this.name = name;
this.domain = domain;
this.client = calculateClient();
}
}
工厂静态方法示例
class Car {
private String name;
private String model;
private Engine engine; // we want to ignore setting this
@Builder
private static Car of(String name, String model){
Car car=new Car();
car.name = name;
car.model = model;
constructEngine(car); // some static private method to construct engine internally
return car;
}
private static void constructEngine(Car car) {
// car.engine = blabla...
// construct engine internally
}
}
那么你可以使用如下:
Car toyotaCorollaCar=Car.builder().name("Toyota").model("Corolla").build();
// You can see now that Car.builder().engine() is not available
注意 每当调用 build() 时都会调用静态方法 of
,因此执行类似 Car.builder().name("Toyota")
的操作实际上不会设置值"Toyota"
到 name
除非调用 build()
然后在构造函数静态方法中分配逻辑 of
被执行。
此外,注意 of
方法是私有访问的,因此 build
方法是调用者唯一可见的方法
我找到了另一种解决方案
您可以将您的字段包装到 initiated final 包装器或代理中。
将它包装成 AtomicReference 的最简单方法。
@Builder
public class Example {
private String field1;
private String field2;
private final AtomicReference<String> excluded = new AtomicReference<>(null);
}
您可以在内部通过get和set方法与它交互,但它不会出现在builder中。
excluded.set("Some value");
excluded.get();
使用 Lombok @Builder
将所谓的 'partial builder' 添加到 class 可以提供帮助。诀窍是像这样添加一个内部部分构建器 class:
@Getter
@Builder
class Human {
private final String name;
private final String surname;
private final Gender gender;
private final String prefix; // Should be hidden, depends on gender
// Partial builder to manage dependent fields, and hidden fields
public static class HumanBuilder {
public HumanBuilder gender(final Gender gender) {
this.gender = gender;
if (Gender.MALE == gender) {
this.prefix = "Mr.";
} else if (Gender.FEMALE == gender) {
this.prefix = "Ms.";
} else {
this.prefix = "";
}
return this;
}
// This method hides the field from external set
private HumanBuilder prefix(final String prefix) {
return this;
}
}
}
PS:@Builder 允许更改生成的构建器 class 名称。上面的示例假定使用默认构建器 class 名称。
我之前使用的一种方法是将实例字段分组为配置字段和会话字段。配置字段作为 class 实例并且对构建器可见,而会话字段进入 嵌套 private static class
并通过具体 final
访问实例字段(Builder 默认会忽略)。
像这样:
@Builder
class XYZClientWrapper{
private String name;
private String domain;
private static class Session {
XYZClient client;
}
private final Session session = new Session();
private void initSession() {
session.client = ...;
}
public void foo() {
System.out.println("name: " + name);
System.out.println("domain: " + domain;
System.out.println("client: " + session.client);
}
}
我有另一种使用 @Delegate
和 Inner Class
的方法,它支持排除字段的“计算值”。
首先,我们将要排除的字段移到 Inner Class
中,以避免 Lombok 将它们包含在构建器中。
然后,我们使用 @Delegate
公开 Getters/Setters 个构建器排除的字段。
示例:
@Builder
@Getter @Setter @ToString
class Person {
private String name;
private int value;
/* ... More builder-included fields here */
@Getter @Setter @ToString
private class BuilderIgnored {
private String position; // Not included in the Builder, and remain `null` until p.setPosition(...)
private String nickname; // Lazy initialized as `name+value`, but we can use setter to set a new value
/* ... More ignored fields here! ... */
public String getNickname(){ // Computed value for `nickname`
if(nickname == null){
nickname = name+value;
}
return nickname;
}
/* ... More computed fields' getters here! ... */
}
@Delegate @Getter(AccessLevel.NONE) // Delegate Lombok Getters/Setters and custom Getters
private final BuilderIgnored ignored = new BuilderIgnored();
}
对于 Person
class 之外的人来说,position
和 nickname
实际上是内部 class 字段。
Person p = Person.builder().name("Test").value(123).build();
System.out.println(p); // Person(name=Test, value=123, ignored=Person.BuilderIgnored(position=null, nickname=Test123))
p.setNickname("Hello World");
p.setPosition("Manager");
System.out.println(p); // Person(name=Test, value=123, ignored=Person.BuilderIgnored(position=Manager, nickname=Hello World))
优点:
- 不要强制排除字段为
final
- 支持排除字段的计算值
- 允许计算字段引用构建器设置的任何字段(换句话说,允许内部 class 是非静态的 class)
- 不需要重复所有字段的列表(例如,列出除构造函数中排除的字段之外的所有字段)
- 不要覆盖 Lombok 库的
@Builder
(例如,创建 MyBuilder extends FooBuilder
)
缺点:
- 被排除的字段实际上是
Inner Class
的字段;但是,使用具有适当 Getters/Setters 的 private
标识符,您可以模仿它们就像是真实字段一样
- 因此,此方法限制您使用 Getters/Setters
访问排除的字段
- 计算值在调用 Getters 时延迟初始化,而不是
.build()
。
要从构建器中排除字段,请尝试使用 @Builder.Default
我喜欢并使用的一种方法是这个。
在构造函数中保留必需参数,并通过构建器设置可选参数。如果所需数量不是很大,则可以使用。
class A {
private int required1;
private int required2;
private int optional1;
private int optional2;
public A(int required1, int required2) {
this.required1 = required1;
this.required2 = required2;
}
@Builder(toBuilder = true)
public A setOptionals(int optional1, int optional2) {
this.optional1 = optional1;
this.optional2 = optional2;
return this;
}
}
然后用
构造它
A a = new A(1, 2).builder().optional1(3).optional2(4).build();
这种方法的好处是可选值也可以有默认值。
我有一个名为 "XYZClientWrapper" 的 class ,它具有以下结构:
@Builder
XYZClientWrapper{
String name;
String domain;
XYZClient client;
}
我想要的 属性 XYZClient client
Lombok 是否支持这样的用例?
是的,您可以将 @Builder 放在构造函数或静态(工厂)方法上,只包含您想要的字段。
披露:我是 Lombok 开发人员。
或者,我发现将字段标记为 final、static 或 static final 指示 @Builder
忽略此字段。
@Builder
public class MyClass {
private String myField;
private final String excludeThisField = "bar";
}
龙目岛 1.16.10
在代码中创建构建器并为您的 属性 添加私有 setter。
@Builder
XYZClientWrapper{
String name;
String domain;
XYZClient client;
public static class XYZClientWrapperBuilder {
private XYZClientWrapperBuilder client(XYZClient client) { return this; }
}
}
我发现我可以实现一个"shell"的static Builder class,添加我想用私有访问修饰符隐藏的方法,在建设者。同样,我也可以向构建器添加自定义方法。
package com.something;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import java.time.ZonedDateTime;
@Data
@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class MyClass{
//The builder will generate a method for this property for us.
private String anotherProperty;
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "localDateTime", column = @Column(name = "some_date_local_date_time")),
@AttributeOverride(name = "zoneId", column = @Column(name = "some__date_zone_id"))
})
@Getter(AccessLevel.PRIVATE)
@Setter(AccessLevel.PRIVATE)
private ZonedDateTimeEmbeddable someDateInternal;
public ZonedDateTime getSomeDate() {
return someDateInternal.toZonedDateTime();
}
public void setSomeDate(ZonedDateTime someDate) {
someDateInternal = new ZonedDateTimeEmbeddable(someDate);
}
public static class MyClassBuilder {
//Prevent direct access to the internal private field by pre-creating builder method with private access.
private MyClassBuilder shipmentDateInternal(ZonedDateTimeEmbeddable zonedDateTimeEmbeddable) {
return this;
}
//Add a builder method because we don't have a field for this Type
public MyClassBuilder someDate(ZonedDateTime someDate) {
someDateInternal = new ZonedDateTimeEmbeddable(someDate);
return this;
}
}
}
这是我的首选解决方案。有了它,您可以在最后创建您的字段 client
并根据先前由构建器设置的其他字段。
XYZClientWrapper{
String name;
String domain;
XYZClient client;
@Builder
public XYZClientWrapper(String name, String domain) {
this.name = name;
this.domain = domain;
this.client = calculateClient();
}
}
工厂静态方法示例
class Car {
private String name;
private String model;
private Engine engine; // we want to ignore setting this
@Builder
private static Car of(String name, String model){
Car car=new Car();
car.name = name;
car.model = model;
constructEngine(car); // some static private method to construct engine internally
return car;
}
private static void constructEngine(Car car) {
// car.engine = blabla...
// construct engine internally
}
}
那么你可以使用如下:
Car toyotaCorollaCar=Car.builder().name("Toyota").model("Corolla").build();
// You can see now that Car.builder().engine() is not available
注意 每当调用 build() 时都会调用静态方法 of
,因此执行类似 Car.builder().name("Toyota")
的操作实际上不会设置值"Toyota"
到 name
除非调用 build()
然后在构造函数静态方法中分配逻辑 of
被执行。
此外,注意 of
方法是私有访问的,因此 build
方法是调用者唯一可见的方法
我找到了另一种解决方案 您可以将您的字段包装到 initiated final 包装器或代理中。 将它包装成 AtomicReference 的最简单方法。
@Builder
public class Example {
private String field1;
private String field2;
private final AtomicReference<String> excluded = new AtomicReference<>(null);
}
您可以在内部通过get和set方法与它交互,但它不会出现在builder中。
excluded.set("Some value");
excluded.get();
使用 Lombok @Builder
将所谓的 'partial builder' 添加到 class 可以提供帮助。诀窍是像这样添加一个内部部分构建器 class:
@Getter
@Builder
class Human {
private final String name;
private final String surname;
private final Gender gender;
private final String prefix; // Should be hidden, depends on gender
// Partial builder to manage dependent fields, and hidden fields
public static class HumanBuilder {
public HumanBuilder gender(final Gender gender) {
this.gender = gender;
if (Gender.MALE == gender) {
this.prefix = "Mr.";
} else if (Gender.FEMALE == gender) {
this.prefix = "Ms.";
} else {
this.prefix = "";
}
return this;
}
// This method hides the field from external set
private HumanBuilder prefix(final String prefix) {
return this;
}
}
}
PS:@Builder 允许更改生成的构建器 class 名称。上面的示例假定使用默认构建器 class 名称。
我之前使用的一种方法是将实例字段分组为配置字段和会话字段。配置字段作为 class 实例并且对构建器可见,而会话字段进入 嵌套 private static class
并通过具体 final
访问实例字段(Builder 默认会忽略)。
像这样:
@Builder
class XYZClientWrapper{
private String name;
private String domain;
private static class Session {
XYZClient client;
}
private final Session session = new Session();
private void initSession() {
session.client = ...;
}
public void foo() {
System.out.println("name: " + name);
System.out.println("domain: " + domain;
System.out.println("client: " + session.client);
}
}
我有另一种使用 @Delegate
和 Inner Class
的方法,它支持排除字段的“计算值”。
首先,我们将要排除的字段移到 Inner Class
中,以避免 Lombok 将它们包含在构建器中。
然后,我们使用 @Delegate
公开 Getters/Setters 个构建器排除的字段。
示例:
@Builder
@Getter @Setter @ToString
class Person {
private String name;
private int value;
/* ... More builder-included fields here */
@Getter @Setter @ToString
private class BuilderIgnored {
private String position; // Not included in the Builder, and remain `null` until p.setPosition(...)
private String nickname; // Lazy initialized as `name+value`, but we can use setter to set a new value
/* ... More ignored fields here! ... */
public String getNickname(){ // Computed value for `nickname`
if(nickname == null){
nickname = name+value;
}
return nickname;
}
/* ... More computed fields' getters here! ... */
}
@Delegate @Getter(AccessLevel.NONE) // Delegate Lombok Getters/Setters and custom Getters
private final BuilderIgnored ignored = new BuilderIgnored();
}
对于 Person
class 之外的人来说,position
和 nickname
实际上是内部 class 字段。
Person p = Person.builder().name("Test").value(123).build();
System.out.println(p); // Person(name=Test, value=123, ignored=Person.BuilderIgnored(position=null, nickname=Test123))
p.setNickname("Hello World");
p.setPosition("Manager");
System.out.println(p); // Person(name=Test, value=123, ignored=Person.BuilderIgnored(position=Manager, nickname=Hello World))
优点:
- 不要强制排除字段为
final
- 支持排除字段的计算值
- 允许计算字段引用构建器设置的任何字段(换句话说,允许内部 class 是非静态的 class)
- 不需要重复所有字段的列表(例如,列出除构造函数中排除的字段之外的所有字段)
- 不要覆盖 Lombok 库的
@Builder
(例如,创建MyBuilder extends FooBuilder
)
缺点:
- 被排除的字段实际上是
Inner Class
的字段;但是,使用具有适当 Getters/Setters 的private
标识符,您可以模仿它们就像是真实字段一样 - 因此,此方法限制您使用 Getters/Setters 访问排除的字段
- 计算值在调用 Getters 时延迟初始化,而不是
.build()
。
要从构建器中排除字段,请尝试使用 @Builder.Default
我喜欢并使用的一种方法是这个。 在构造函数中保留必需参数,并通过构建器设置可选参数。如果所需数量不是很大,则可以使用。
class A {
private int required1;
private int required2;
private int optional1;
private int optional2;
public A(int required1, int required2) {
this.required1 = required1;
this.required2 = required2;
}
@Builder(toBuilder = true)
public A setOptionals(int optional1, int optional2) {
this.optional1 = optional1;
this.optional2 = optional2;
return this;
}
}
然后用
构造它A a = new A(1, 2).builder().optional1(3).optional2(4).build();
这种方法的好处是可选值也可以有默认值。