Searching for a plugin to avoid boilerplate code to implement Joshua Bloch 's builder pattern I found the amazing Lombok Project which enables you to generate builders via annotations like this:
@Builder
public class Person {
private String name;
private String address;
private String secondAddress;
}
PersonBuilder.builder().name("yourName").address("your Address").build();
As you can see, there is no boilerplate code, and you can easily create an instance of Person
by calling the provided static builder()
method, chaining setter-like-calls just like it works with JavaBeans-Pattern and end the chain with calling build()
;
One of the disadvantages of the JavaBeans-Pattern compared to builder pattern is (from Effective Java ):
Because construction is split across multiple calls, a JavaBean may be in an inconsistent state partway through its construction .
Assuming that in the above example, the first two attributes, name and address, are mandatory for constructing an instance of Person, the way Lombok implements the builder pattern enables a developer to split/shorten the construction and do something with a possibly inconsistent instance of Person
, like this:
Person p = PersonBuilder.builder().name("yourName").build();
...
System.out.println(p.getAddress());
...
p.setAddress("your address");
Joshua Bloch 's solution prefers a builder method with the mandatory attributes as parameters, so that there is no possibility that the construction is split across multiple calls, like illustrated in Item 2: Consider a builder when faced with many constructor parameters .
My question is: Is there any convenient way like annotation parameters for @Builder or something like Springs @Required or @Mandatory on attribute level to enforce Lombok to avoid offering the parameterless builder constructor and to provide a constructor with the mandatory parameters, as Joshua Bloch proposes?
I've tried many options from @Builder documentation but couldn't find a desirable solution.
What works for me is described as follows:
It's a bit of boilerplate, which could possibly be avoided. See my solution applied on Joshua Bloch's example bellow.
/**
* 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();
}
}
As per @Builder
docs this annotation can work together with @NonNull
. If the field marked @NonNull
is null
you will get an NullPointerException
preventing creation of invalid object:
@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
}
To go further you can define the builder
method yourself. If the method is present Lombok won't generate it and you can now force the arguments compile time.
@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();
}
However one could still create a builder by writing new Person.PersonBuilder()
because the builder class is still accessible.
Also, in extension can be used:
@Builder.Default <modifier><instanceVariable>=<default-value>
,
like: @Builder.Default private String myVariable = ""
.
Please read this: @Builder default properties
Avoids compiler errors asking for required properties that won't be set in some scenarios such as persistence, indexing, etc. (update/deleteBy/findBy from id or reduced properties set, without having a full object graph).
It's an elegant solution for not overriding .build() or setters as suggested.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.