Let's consider the following Entity:
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode(callSuper = true)
public class Audi extends Car {
private String name;
private String headquarter;
private BigDecimal revenue = BigDecimal.ZERO;
private BigDecimal totalAssets = BigDecimal.ZERO;
}
I want to have all BigDecimal variables with BigDecimal.ZERO as default value.
If I initialize it immediately after declaration,
I got this warning:
#Builder will ignore the initializing expression entirely. If you want
the initializing expression to serve as default, add #Builder.Default.
If it is not supposed to be settable during building, make the field
final.
For an Entity with 10/15 BigDecimal, it's not so beautiful to have this annotation on each field.
Any alternative?
As Michael pointed out, you don't have many other options. If you've added the #Builder annotation, you must use #Builder.Default for each field in order to utilize the fluent builder API.
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode(callSuper = true)
public class Audi extends Car {
private String name;
private String headquarter;
#Builder.Default
private BigDecimal revenue = BigDecimal.ZERO;
#Builder.Default
private BigDecimal totalAssets = BigDecimal.ZERO;
}
Usage:
Audi audi = Audi.builder()
.name("Audi A3")
.headquarter("Germany")
.build();
System.out.println(audi);
Output:
Audi(name=Audi A3, headquarter=Germany, revenue=0, totalAssets=0)
If builder functionality is not needed, objects can be instantiated in the traditional Java way using a constructor. However, in this case, the #Builder annotation should be removed and default values must be set directly in the constructor or during declaration.
#Data
#NoArgsConstructor
#RequiredArgsConstructor
#EqualsAndHashCode(callSuper = true)
public class Audi extends Car {
#NonNull
private String name;
#NonNull
private String headquarter;
private BigDecimal revenue = BigDecimal.ZERO;
private BigDecimal totalAssets = BigDecimal.ZERO;
}
Usage:
final Audi audi = new Audi("Audi A3", "Germany");
System.out.println(audi);
Output:
Audi(name=Audi A3, headquarter=Germany, revenue=0, totalAssets=0)
Related
I am currently trying to remove some boilerplate code with lombok but have some trouble.
I have an abstract class AbstractParent,
#SuperBuilder(toBuilder = true)
#EqualsAndHashCode
#ToString
#Getter
#Setter
public abstract class AbstractParent {
private final field1;
private final field2;
then I have a Child Class like this
#SuperBuilder(toBuilder = true)
#EqualsAndHashCode(callSuper = true)
#ToString(callSuper = true)
public abstract class Child extends AbstractParent {
And I also have some classes extending the Child class
#SuperBuilder(toBuilder = true)
#EqualsAndHashCode(callSuper = true)
#ToString(callSuper = true)
#Component
public abstract class ExtendedChild extends Child {
private final field1;
private final field2;
Since lombok can't use super in a constructor, I tried the #SuperBuilder Annotation instead of defining the Constructors manually but can't get the Application to start. Am I missing something completely? Is this even possible with lombok and spring?
The Error is:
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in com.fu.extendedChild required a bean of type 'com.fu.extendedChild$extendedChildBuilder' that could not be found.
Action:
Consider defining a bean of type 'com.fu.extendedChild$extendedChildBuilder' in your configuration.
I was able to reproduce your problem with this code
#SuperBuilder(toBuilder = true)
#EqualsAndHashCode
#ToString
#Getter
#Setter
public abstract class AbstractParent {
private final String field1;
private final String field2;
}
#SuperBuilder(toBuilder = true)
#EqualsAndHashCode(callSuper = true)
#ToString(callSuper = true)
abstract class Child extends AbstractParent {
}
#SuperBuilder(toBuilder = true)
#EqualsAndHashCode(callSuper = true)
#ToString(callSuper = true)
#Component
class ExtendedChild extends Child {
private final String field1;
private final String field2;
}
What the #SuperBuilder does here on class ExtendedChild is
protected ExtendedChild(ExtendedChildBuilder<?, ?> b) {
super(b);
this.field1 = b.field1;
this.field2 = b.field2;
}
So it says you need an ExtendedChildBuilder instance in order to build an ExtendedChild instance. In order words, you have to have a builder in your spring context to be able to create your object.
This is not a good idea since a builder is stateful and not thread-safe. Furthermore, the builder pattern is here to be able to provide values whenever you need them before constructing your object. Using a builder as a Spring bean denies that advantage.
If this is immutability you want to achieve, then using plain old constructors with the right parameters is way better (and when done right, this is not boilerplate code, this is good design).
Then, Spring injection will be a child's play.
Please do not trade complexity for the sake of writing less code :)
How to model this in Lombok, constructors which take lesser arguments and supporting get/set for transient methods. How to tweak the following definitions
#NoArgsConstructor
#AllArgsConstructor
#Data
public class SalaryRange {
private Integer from;
private Integer to;
private transient String displayName;
private SalaryRange() {
}
private SalaryRange(Integer from) {
this(from, null);
}
private SalaryRange(Integer from, Integer to) {
this(from, to, null);
}
private SalaryRange(Integer from, Integer to, String displayName) {
this.from = from;
this.to = to;
this.displayName = displayName;
}
..
}
Just use a Builder and NoArgsConstructor
#NoArgsConstructor
#Builder
#Data
public class SalaryRange {
private Integer from;
private Integer to;
private transient String displayName;
}
and then
SalaryRange range = SalaryRange.builder().from(1).to(2).build();
Documentation:
Project Lombok Builder
One additional notice - when using #Builder do not static import Builder class - there is a bug that, as far as I know, is not fixed yet
static import not working in lombok builder in intelliJ
I don't think Lombok provide that much flexibility with constructor annotation. 2 approaches may help:
Use #RequiredArgsConstructor, which takes in all final variables.
#NoArgsConstructor
#AllArgsConstructor
#Data
public class SalaryRange {
private final Integer from;
private final Integer to;
private transient String displayName;
}
Use #Builder to achieve more flexibility. However, you may still need #NoArgsConstructor #AllArgsConstructor sometimes for serialization/deserialization by thirty party tools integration, e.g. spring rest, mybatis, etc. Just try it out and check logs.
Otherwise, just write constructor manually, intellij can generate constructor for you quickly by command + N
You may keep constructors from below, the other two are generated by #NoArgsConstructor and #AllArgsConstructor, constructors are meant to build objects
private SalaryRange(Integer from) {
this(from, null);
}
private SalaryRange(Integer from, Integer to) {
this(from, to, null);
}
Another approach might be to combine #Accessors (experimental feature) with #NoArgsConstructor and #AllArgsConstructor
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Accessors(chain = true)
public class SalaryRange {
private Integer from;
private Integer to;
private transient String displayName;
}
SalaryRange salaryRange1 = new SalaryRange().setFrom(1).setTo(2);
SalaryRange salaryRange2 = new SalaryRange(1, 2, "somename");
You may use #Accessors(chain = true, fluent = true) to have fluent accessors if you don't require the get & set prefix
SalaryRange salaryRange1 = new SalaryRange().from(1).to(2);
PD: Builder pattern has downsides that might lead to an Anti-Pattern and might hide a bad design, so be careful when you decide to use it. I think Builder pattern is overused in this scenario, we don't need to add extra complexity to build an object with three attributes.
https://www.baeldung.com/lombok-accessors
https://www.yegor256.com/2016/02/03/design-patterns-and-anti-patterns.html
I have a my_table with a composite sort key made of two combined attributes id and model_name (i.e., id_model_name, similarly from what is done here here and here).
So I created this Java model:
#Builder
#Data
#AllArgsConstructor
#NoArgsConstructor
#DynamoDBTable(tableName = "my_table")
public class TableModel {
private static final String COMPOSITE_KEY_SEPARATOR = "_";
#DynamoDBAttribute(attributeName = "id")
private String id;
#DynamoDBAttribute(attributeName = "model_name")
private String modelName;
#DynamoDBRangeKey(attributeName = "id_model_name")
public String getIdModelName() {
return String.format("%s%s%s", id, COMPOSITE_KEY_SEPARATOR, modelName);
}
// more stuff...
}
However I'm getting:
DynamoDBMappingException: DRTFacet[id_model_name]; could not unconvert attribute
Notice that there is no String idModelName field, because it could mess up with #AllArgsConstructor and #Builder (since it's a derived field). Is it because this field is missing (together with a setter method?). How can I overcome this?
I found out that providing a dummy setter solved the problem:
public void setIdModelName(final String idModelName) {}
I have this simple class
public class ErrorDetails {
private String param = null;
private String moreInfo = null;
private String reason = null;
...
}
After refactoring, I added #Data and #Builder, but all the instantiations doesn't work any more
ErrorDetails errorDetails = new ErrorDetails();
'ErrorDetails(java.lang.String, java.lang.String, java.lang.String)'
is not public in
'com.nordea.openbanking.payments.common.ndf.client.model.error.ErrorDetails'.
Cannot be accessed from outside package
If I removed #Builder, then it will work fine,
Why I cannot use #Data and #Builder together?
Lombok's #Builder must have #AllArgsConstructor in order to work
Adding also #AllArgsConstructor should do
Under the hood it build all fields using constructor with all fields
applying #Builder to a class is as if you added #AllArgsConstructor(access = AccessLevel.PACKAGE) to the class and applied the #Builder annotation to this all-args-constructor. This only works if you haven't written any explicit constructors yourself.
The full config should be :
#Data
#Builder(toBuilder = true)
#AllArgsConstructor
#NoArgsConstructor
class ErrorDetails {
private String param; // no need to initiate with null
private String moreInfo;
private String reason;
}
I was setting the value of recordId from the child classes using the default constructor and was not using lombok #Builder initially. Eventually i decided to use the Builder here, but the problem now is lombok Builder overrides my default constructor internally hence the value is never set.
How can I put any hook too make lombok #Builder use my default constructor?
Parent class:
#Getter
#Setter
public abstract class Record {
private String recordId;
}
Child class:
#Getter
#Setter
#Builder
#ToString
#AllArgsConstructor
public class SRecord extends Record {
private static final String RECORD_ID = "REC001";
private String street;
private String city;
public SRecord() {
setRecordId(RECORD_ID); //value of recordId being set
}
}
Lombok's #Builder simply does not use the default constructor. It passes its values to an all-args constructor so that this constructor can fill the new instance with these values. #Builder does not use setters or direct access to the fields to do so. So your default constructor is simply ignored by #Builder.
What you can do is write your own all-args constructor. In it, you set your value for recordId and assign the rest of the fields from the parameters.
I think you should create a constructor in your base class:
#Getter
#Setter
public abstract class Record {
private String recordId;
public Record(String recordId) {
this.recordId = recordId;
}
}
Then use it in the constructor of the inherited class:
#Getter
#Setter
#Builder
public class SRecord extends Record {
private static final String RECORD_ID = "REC001";
private String street;
private String city;
public SRecord(String street, String city) {
super(RECORD_ID);
this.street = street;
this.city = city;
}
}
P.S. If you want to use Lombok Builder with inheritance you can use this technique.