Lombok @Builder 的必需参数
Required arguments with a Lombok @Builder
如果我将 @Builder 添加到 class。生成器方法已创建。
Person.builder().name("john").surname("Smith").build();
我有一个需要特定字段的要求。在这种情况下,名称字段是必需的,但姓氏不是。理想情况下,我想这样声明。
Person.builder("john").surname("Smith").build()
我不知道该怎么做。我尝试将 @Builder 添加到构造函数中,但没有成功。
@Builder
public Person(String name) {
this.name = name;
}
您可以使用 Lombok 注释配置轻松完成
import lombok.Builder;
import lombok.ToString;
@Builder(builderMethodName = "hiddenBuilder")
@ToString
public class Person {
private String name;
private String surname;
public static PersonBuilder builder(String name) {
return hiddenBuilder().name(name);
}
}
然后就这样使用
Person p = Person.builder("Name").surname("Surname").build();
System.out.println(p);
当然@ToString
这里是可选的
这是另一种方法:
@Builder()
@Getter
@ToString
public class Person {
private final String name;
private final String surname;
public static PersonBuilder builder(String name){
return new PersonBuilder().name(name);
}
public static void main(String[] args) {
Person p = Person.builder("John Doe")
.surname("Bill")
.build();
}
}
我建议不要使用这种方法,因为您很难将它始终如一地应用于其他对象。相反,您可以只使用 @lombok.NonNull
注释标记字段,Lombok 将在构造函数和设置器中为您生成空检查,因此如果未设置这些字段,Builder.build()
将失败。
使用构建器模式可以让您非常清楚地识别要为哪些字段设置哪些值。在您的示例中,名称字段已经丢失,如果您正在构建具有多个必填字段的对象,所有其他必填字段将进一步丢失。考虑下面的例子,你能通过阅读代码分辨出哪个字段是哪个字段吗?
Person.builder("John", "Michael", 16, 1987) // which is name, which is surname? what is 16?
.year(1982) // if this is year of birth, then what is 1987 above?
.build()
这是我对问题的解决方案
import lombok.Builder;
import lombok.Data;
import lombok.NonNull;
@Data
@Builder(builderMethodName = "privateBuilder")
public class Person {
@NonNull
private String name;
@NonNull
private String surname;
private int age;//optional
public static Url safeBuilder() {
return new Builder();
}
interface Url {
Surname name(String name);
}
interface Surname {
Build surname(String surname);
}
interface Build {
Build age(int age);
Person build();
}
public static class Builder implements Url, Surname, Build {
PersonBuilder pb = Person.privateBuilder();
@Override
public Surname name(String name) {
pb.name(name);
return this;
}
@Override
public Build surname(String surname) {
pb.surname(surname);
return this;
}
@Override
public Build age(int age) {
pb.age(age);
return this;
}
@Override
public Person build() {
return pb.build();
}
}
}
受此博客启发 post:
https://blog.jayway.com/2012/02/07/builder-pattern-with-a-twist/
最简单的解决方案是为所有必填值添加一个 @lombok.NonNull
。如果未设置必填字段,Builder 将无法构建对象。
这是一个 JUnit 测试,用于演示 final
和 @NonNull
的所有组合的行为:
import static org.junit.Assert.fail;
import org.junit.Test;
import lombok.Builder;
import lombok.ToString;
public class BuilderTests {
@Test
public void allGiven() {
System.err.println(Foo.builder()
.nonFinalNull("has_value")
.nonFinalNonNull("has_value")
.finalNull("has_value")
.finalNonNull("has_value")
.build());
}
@Test
public void noneGiven() {
try {
System.err.println(Foo.builder()
.build()
.toString());
fail();
} catch (NullPointerException e) {
// expected
}
}
@Test
public void nonFinalNullOmitted() {
System.err.println(Foo.builder()
.nonFinalNonNull("has_value")
.finalNull("has_value")
.finalNonNull("has_value")
.build());
}
@Test
public void nonFinalNonNullOmitted() {
try {
System.err.println(Foo.builder()
.nonFinalNull("has_value")
.finalNull("has_value")
.finalNonNull("has_value")
.build());
fail();
} catch (NullPointerException e) {
// expected
}
}
@Test
public void finalNullOmitted() {
System.err.println(Foo.builder()
.nonFinalNull("has_value")
.nonFinalNonNull("has_value")
.finalNonNull("has_value")
.build());
}
@Test
public void finalNonNullOmitted() {
try {
System.err.println(Foo.builder()
.nonFinalNull("has_value")
.nonFinalNonNull("has_value")
.finalNull("has_value")
.build());
fail();
} catch (NullPointerException e) {
// expected
}
}
@Builder
@ToString
private static class Foo {
private String nonFinalNull;
@lombok.NonNull
private String nonFinalNonNull;
private final String finalNull;
@lombok.NonNull
private final String finalNonNull;
}
}
让凯文·戴的 更进一步:
@Builder
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE) // If immutability is desired
@ToString
public class Person {
@NonNull // Presumably name cannot be null since its required by the builder
private final String name;
private final String surname;
private static PersonBuilder builder() {
return new PersonBuilder();
}
public static PersonBuilder builder(String name){
return builder().name(name);
}
}
这并不理想,但它提供了编译时强制执行,并且此 class 的调用者将只有一个构建器方法可以使用。
如果你需要这个功能,你可以自己定制构建器class,你仍然可以添加@Builder
注解。
@Builder
public class Person {
public static class PersonBuilder {
private String name;
private PersonBuilder() {
}
public PersonBuilder(final String name) {
this.name = name;
}
}
private static PersonBuilder builder() {
return null; // or we can throw exception.
}
public static PersonBuilder builder(final String name) {
return new PersonBuilder(clientId);
}
}
以User
class为例,id
字段必填:
@AllArgsConstructor(access = AccessLevel.PRIVATE) // required, see
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Builder
@Getter
public class User {
private String id;
private String name;
private int age;
public static UserBuilder builder(final String id) {
return new UserBuilder().id(id);
}
}
您只能 像User user = User.builder("id-123").name("Tom").build;
这样的构建器初始化一个User
实例。使用 private no args 构造函数,您无法 User user = new User();
或 User user = new User("id-123");
因此您始终需要传递所需的参数 id
。请注意初始化的实例是不可变的。
结合@Pawel 的回答和 Max 的评论 ...
import lombok.Builder;
import lombok.ToString;
@Builder
public class Person {
private String name;
private String surname;
public static PersonBuilder builder(String name) {
return new PersonBuilder().name(name);
}
}
最佳实践:
import lombok.Builder;
import lombok.NonNull;
@Builder(builderMethodName = "privateBuilder")
public class Person {
@NonNull
private String name;
private String surname;
public static class PersonNameBuilder {
public PersonBuilder name(String name) {
return Person.privateBuilder().name(status);
}
}
public static PersonNameBuilder builder(String name) {
return new PersonNameBuilder();
}
private static PersonBuilder privateBuilder(){
return new PersonBuilder();
}
}
用法:
PersonNameBuilder nameBuilder = Person.builder();
PersonBuilder builder = nameBuilder.name("John");
Person p1 = builder.surname("Smith").build();
// Or
Person p2 = Person.builder().name("John").surname("Smith").build();
尽管我很想拥有编译时验证功能,但库的作者已经明确表示 the feature probably won't be added.
所以我对此的看法是,拥有这样的东西。
@Builder
public class Person {
String name;
Integer age;
Optional optional;
@Builder
public class Optional {
String surname;
String companyName;
String spouseName;
}
}
而且你可以像这样使用它
Person p = Person.builder()
.age(40)
.name("David")
.optional(Person.Optional.builder()
.surname("Lee")
.companyName("Super Company")
.spouseName("Emma")
.build())
.build();
不,没有验证。
但从图书馆用户的角度来看,
很清楚什么是必需的,什么不是,并且能够在不查看文档的情况下构建对象实例。
这是 Pawel 响应的灵感,带有一个隐藏生成的构建器:
import lombok.Builder;
import lombok.ToString;
@Builder(builderMethodName = "")
@ToString
public class Person {
private String name;
private String surname;
public static PersonBuilder builder(String name) {
return new PersonBuilder().name(name);
}
}
为了使lombok的构建器实现隐含的限制和风险尽可能明显,从而减少错误滥用的可能性,我提出以下解决方案:
import lombok.Builder;
@Builder(builderClassName = "UnsafeBuilder")
public class Person {
private String name;
private String surname;
public static UnsafeBuilder builder(String name) {
return new UnsafeBuilder().name(name);
}
}
按预期使用此解决方案非常简单:
Person thePerson = Person.builder("the_name").surname("the_surname").build();
以非预期方式使用生成器的唯一方法使风险显而易见,而且很可能不会被错误选择:
final Person unsafePerson = new Person.UnsafeBuilder().surname("the_surname").build();
class 名称当然可以更激进地选择 - 例如 NeverCallThisConstructor
.
如果我将 @Builder 添加到 class。生成器方法已创建。
Person.builder().name("john").surname("Smith").build();
我有一个需要特定字段的要求。在这种情况下,名称字段是必需的,但姓氏不是。理想情况下,我想这样声明。
Person.builder("john").surname("Smith").build()
我不知道该怎么做。我尝试将 @Builder 添加到构造函数中,但没有成功。
@Builder
public Person(String name) {
this.name = name;
}
您可以使用 Lombok 注释配置轻松完成
import lombok.Builder;
import lombok.ToString;
@Builder(builderMethodName = "hiddenBuilder")
@ToString
public class Person {
private String name;
private String surname;
public static PersonBuilder builder(String name) {
return hiddenBuilder().name(name);
}
}
然后就这样使用
Person p = Person.builder("Name").surname("Surname").build();
System.out.println(p);
当然@ToString
这里是可选的
这是另一种方法:
@Builder()
@Getter
@ToString
public class Person {
private final String name;
private final String surname;
public static PersonBuilder builder(String name){
return new PersonBuilder().name(name);
}
public static void main(String[] args) {
Person p = Person.builder("John Doe")
.surname("Bill")
.build();
}
}
我建议不要使用这种方法,因为您很难将它始终如一地应用于其他对象。相反,您可以只使用 @lombok.NonNull
注释标记字段,Lombok 将在构造函数和设置器中为您生成空检查,因此如果未设置这些字段,Builder.build()
将失败。
使用构建器模式可以让您非常清楚地识别要为哪些字段设置哪些值。在您的示例中,名称字段已经丢失,如果您正在构建具有多个必填字段的对象,所有其他必填字段将进一步丢失。考虑下面的例子,你能通过阅读代码分辨出哪个字段是哪个字段吗?
Person.builder("John", "Michael", 16, 1987) // which is name, which is surname? what is 16?
.year(1982) // if this is year of birth, then what is 1987 above?
.build()
这是我对问题的解决方案
import lombok.Builder;
import lombok.Data;
import lombok.NonNull;
@Data
@Builder(builderMethodName = "privateBuilder")
public class Person {
@NonNull
private String name;
@NonNull
private String surname;
private int age;//optional
public static Url safeBuilder() {
return new Builder();
}
interface Url {
Surname name(String name);
}
interface Surname {
Build surname(String surname);
}
interface Build {
Build age(int age);
Person build();
}
public static class Builder implements Url, Surname, Build {
PersonBuilder pb = Person.privateBuilder();
@Override
public Surname name(String name) {
pb.name(name);
return this;
}
@Override
public Build surname(String surname) {
pb.surname(surname);
return this;
}
@Override
public Build age(int age) {
pb.age(age);
return this;
}
@Override
public Person build() {
return pb.build();
}
}
}
受此博客启发 post:
https://blog.jayway.com/2012/02/07/builder-pattern-with-a-twist/
最简单的解决方案是为所有必填值添加一个 @lombok.NonNull
。如果未设置必填字段,Builder 将无法构建对象。
这是一个 JUnit 测试,用于演示 final
和 @NonNull
的所有组合的行为:
import static org.junit.Assert.fail;
import org.junit.Test;
import lombok.Builder;
import lombok.ToString;
public class BuilderTests {
@Test
public void allGiven() {
System.err.println(Foo.builder()
.nonFinalNull("has_value")
.nonFinalNonNull("has_value")
.finalNull("has_value")
.finalNonNull("has_value")
.build());
}
@Test
public void noneGiven() {
try {
System.err.println(Foo.builder()
.build()
.toString());
fail();
} catch (NullPointerException e) {
// expected
}
}
@Test
public void nonFinalNullOmitted() {
System.err.println(Foo.builder()
.nonFinalNonNull("has_value")
.finalNull("has_value")
.finalNonNull("has_value")
.build());
}
@Test
public void nonFinalNonNullOmitted() {
try {
System.err.println(Foo.builder()
.nonFinalNull("has_value")
.finalNull("has_value")
.finalNonNull("has_value")
.build());
fail();
} catch (NullPointerException e) {
// expected
}
}
@Test
public void finalNullOmitted() {
System.err.println(Foo.builder()
.nonFinalNull("has_value")
.nonFinalNonNull("has_value")
.finalNonNull("has_value")
.build());
}
@Test
public void finalNonNullOmitted() {
try {
System.err.println(Foo.builder()
.nonFinalNull("has_value")
.nonFinalNonNull("has_value")
.finalNull("has_value")
.build());
fail();
} catch (NullPointerException e) {
// expected
}
}
@Builder
@ToString
private static class Foo {
private String nonFinalNull;
@lombok.NonNull
private String nonFinalNonNull;
private final String finalNull;
@lombok.NonNull
private final String finalNonNull;
}
}
让凯文·戴的
@Builder
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE) // If immutability is desired
@ToString
public class Person {
@NonNull // Presumably name cannot be null since its required by the builder
private final String name;
private final String surname;
private static PersonBuilder builder() {
return new PersonBuilder();
}
public static PersonBuilder builder(String name){
return builder().name(name);
}
}
这并不理想,但它提供了编译时强制执行,并且此 class 的调用者将只有一个构建器方法可以使用。
如果你需要这个功能,你可以自己定制构建器class,你仍然可以添加@Builder
注解。
@Builder
public class Person {
public static class PersonBuilder {
private String name;
private PersonBuilder() {
}
public PersonBuilder(final String name) {
this.name = name;
}
}
private static PersonBuilder builder() {
return null; // or we can throw exception.
}
public static PersonBuilder builder(final String name) {
return new PersonBuilder(clientId);
}
}
以User
class为例,id
字段必填:
@AllArgsConstructor(access = AccessLevel.PRIVATE) // required, see
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Builder
@Getter
public class User {
private String id;
private String name;
private int age;
public static UserBuilder builder(final String id) {
return new UserBuilder().id(id);
}
}
您只能 像User user = User.builder("id-123").name("Tom").build;
这样的构建器初始化一个User
实例。使用 private no args 构造函数,您无法 User user = new User();
或 User user = new User("id-123");
因此您始终需要传递所需的参数 id
。请注意初始化的实例是不可变的。
结合@Pawel 的回答和 Max 的评论 ...
import lombok.Builder;
import lombok.ToString;
@Builder
public class Person {
private String name;
private String surname;
public static PersonBuilder builder(String name) {
return new PersonBuilder().name(name);
}
}
最佳实践:
import lombok.Builder;
import lombok.NonNull;
@Builder(builderMethodName = "privateBuilder")
public class Person {
@NonNull
private String name;
private String surname;
public static class PersonNameBuilder {
public PersonBuilder name(String name) {
return Person.privateBuilder().name(status);
}
}
public static PersonNameBuilder builder(String name) {
return new PersonNameBuilder();
}
private static PersonBuilder privateBuilder(){
return new PersonBuilder();
}
}
用法:
PersonNameBuilder nameBuilder = Person.builder();
PersonBuilder builder = nameBuilder.name("John");
Person p1 = builder.surname("Smith").build();
// Or
Person p2 = Person.builder().name("John").surname("Smith").build();
尽管我很想拥有编译时验证功能,但库的作者已经明确表示 the feature probably won't be added.
所以我对此的看法是,拥有这样的东西。
@Builder
public class Person {
String name;
Integer age;
Optional optional;
@Builder
public class Optional {
String surname;
String companyName;
String spouseName;
}
}
而且你可以像这样使用它
Person p = Person.builder()
.age(40)
.name("David")
.optional(Person.Optional.builder()
.surname("Lee")
.companyName("Super Company")
.spouseName("Emma")
.build())
.build();
不,没有验证。 但从图书馆用户的角度来看, 很清楚什么是必需的,什么不是,并且能够在不查看文档的情况下构建对象实例。
这是 Pawel 响应的灵感,带有一个隐藏生成的构建器:
import lombok.Builder;
import lombok.ToString;
@Builder(builderMethodName = "")
@ToString
public class Person {
private String name;
private String surname;
public static PersonBuilder builder(String name) {
return new PersonBuilder().name(name);
}
}
为了使lombok的构建器实现隐含的限制和风险尽可能明显,从而减少错误滥用的可能性,我提出以下解决方案:
import lombok.Builder;
@Builder(builderClassName = "UnsafeBuilder")
public class Person {
private String name;
private String surname;
public static UnsafeBuilder builder(String name) {
return new UnsafeBuilder().name(name);
}
}
按预期使用此解决方案非常简单:
Person thePerson = Person.builder("the_name").surname("the_surname").build();
以非预期方式使用生成器的唯一方法使风险显而易见,而且很可能不会被错误选择:
final Person unsafePerson = new Person.UnsafeBuilder().surname("the_surname").build();
class 名称当然可以更激进地选择 - 例如 NeverCallThisConstructor
.