java - 使用 Lombok 为具有必需和可选属性的类创建构建器
问题描述
搜索一个插件以避免样板代码来实现Joshua Bloch的构建器模式,我发现了令人惊叹的Lombok 项目,它使您能够通过如下注释生成构建器:
@Builder
public class Person {
private String name;
private String address;
private String secondAddress;
}
PersonBuilder.builder().name("yourName").address("your Address").build();
如您所见,没有样板代码,您可以Person
通过调用提供的静态builder()
方法轻松创建一个实例,链接 setter-like-calls 就像它使用 JavaBeans-Pattern 一样,并以调用结束链build()
;
与构建器模式相比,JavaBeans-Pattern 的缺点之一是(来自Effective Java):
因为构造被拆分为多个调用,所以 JavaBean 在构造过程中可能处于不一致的状态。
假设在上面的示例中,前两个属性 name 和 address 对于构造 Person 的实例是必需的,Lombok 实现构建器模式的方式使开发人员能够拆分/缩短构造并使用可能不一致的实例做一些事情Person
, 像这样:
Person p = PersonBuilder.builder().name("yourName").build();
...
System.out.println(p.getAddress());
...
p.setAddress("your address");
Joshua Bloch的解决方案更喜欢将强制属性作为参数的构建器方法,这样就不可能将构造拆分为多个调用,如第 2 条所示:在面对许多构造函数参数时考虑构建器。
我的问题是: 是否有任何方便的方法,例如 @Builder 的注释参数或属性级别的 Springs @Required 或 @Mandatory 之类的东西,以强制 Lombok 避免提供无参数构建器构造函数并为构造函数提供强制参数,如 Joshua Bloch提议?
我尝试了@Builder 文档中的许多选项,但找不到理想的解决方案。
对我有用的描述如下:
- 用强制参数为 Person 定义一个构造函数,
- 使用强制参数的参数化签名覆盖构建器构造函数。
这是一个样板,可以避免。请参阅我在 Joshua Bloch 的示例中应用的解决方案。
/**
* Uncle Bobs builder example for constructors with many required & optional parameters,
* realized by lombok.
*
*/
@AllArgsConstructor(access=AccessLevel.PRIVATE) // Let lombok generate private c-tor with all parameters, as needed by @Builder.
@Builder(
builderClassName="Builder", // Cosmetic. Without this option, the builder class would have the name NutritionFactsBuilder.
toBuilder=true // Enabling creation of a builder instance based on a main class instance: NutritionFacts.
)
public class NutritionFacts {
// Required parameters
private int servingSize;
private int servings;
// Optional parameters
private int calories;
private int fat;
private int sodium;
private int carbohydrate;
/**
* A builder method demanding required parameters.
*/
public static Builder builder(int servingSize, int servings) {
return new NutritionFacts(servingSize, servings).toBuilder();
}
/**
* eclipse-created C-tor with required parameters.
* Can be public for instantiating, but doesn't have to.
*/
public NutritionFacts(int servingSize, int servings) {
super();
this.servingSize = servingSize;
this.servings = servings;
}
public static void main(String[] args) {
NutritionFacts cocaCola = NutritionFacts.builder(240, 8)
.calories(100)
.sodium(35)
.carbohydrate(27)
.build();
}
}
解决方案
根据@Builder
文档,此注释可以与@NonNull
. 如果标记的字段@NonNull
是null
您将NullPointerException
阻止创建无效对象:
@Builder
static class Person {
@NonNull
private final String name;
@NonNull
private final Integer age;
}
public static void main(String[] args) {
Person.builder()
.name("Fred")
.build(); // java.lang.NullPointerException: age is marked @NonNull but is null
}
要更进一步,您可以builder
自己定义方法。如果该方法存在,Lombok 将不会生成它,您现在可以强制参数编译时间。
@Builder
static class Person {
@NonNull
private final String name;
@NonNull
private final Integer age;
public static PersonBuilder builder(String name, Integer age) {
return new PersonBuilder().name(name).age(age);
}
}
public static void main(String[] args) {
Person.builder("Fred", 11)
.build();
}
然而,仍然可以通过编写来创建构建器,new Person.PersonBuilder()
因为构建器类仍然是可访问的。
推荐阅读
- sql - 在 BigQuery 中将 YYYYDDD 字符串转换为 YYYYMMDD
- ios - DispatchGroup 不适用于 URLSession.shared.dataTask
- python - 为 Python 设置路径(窗口)
- r - 新机器的警告消息“在 Windows 数据库中找不到字体系列”
- sql - 重塑 Postgres 表,从长到宽
- angular - 检查商店数据是否已更改
- python - Python:记住过程序列中的状态
- android - Kotlin 中具有 2 个以上构造函数的 init 块
- python - 从 PyCharm 连接到正在运行的 Spark-Session
- powershell - 用于服务器 2008 的 Powershell 脚本,用于获取 MSMQ 安全属性值