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();
}
}
Project Lombok's @Builder is a helpful mechanism for using the Builder pattern without writing boilerplate code. We can apply this annotation to a Class or a method. In this quick tutorial, we'll look at the different use cases for @Builder.
By using the @Builder annotation, you let Lombok generate the builders for you. By annotating a class with @Builder , Lombok will produce a class implementing the aforementioned builder pattern. For example, by annotating the Author class, an AuthorBuilder class will be automatically generated.
Use @Builder on Constructor Level Then when we put the @Builder annotation on the constructor, Lombok creates a builder class containing only the constructor parameters.
The builder pattern simplifies the creation of objects. It also simplifies the code as your do not have to call a complex constructor or call several setter methods on the created object. The builder pattern can be used to create an immutable class.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With