How to model constructors which use fewer arguments in lombok - java

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

Related

Java Annotation for composite Range Keys in DDB

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) {}

Lombok builder override default constructor

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.

Default field value with #Builder or #Getter annotation in Lombok

I'm using Lombok #Builder annotation, but I'd like some of the String fields to be optional and default to "" to avoid NPEs. Is there an easy way to do this? I can't find anything.
Alternately, a way to customize #Getter to return a default value if the variable is null.
Starting from version v1.16.16 they added #Builder.Default.
#Builder.Default lets you configure default values for your fields
when using #Builder.
example:
#Setter
#Getter
#Builder
public class MyData {
private Long id;
private String name;
#Builder.Default
private Status status = Status.NEW;
}
PS: Nice thing they also add warning in case you didn't use #Builder.Default.
Warning:(35, 22) java: #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.
You have to provide the builder class like the below:
#Builder
public class XYZ {
private String x;
private String y;
private String z;
private static class XYZBuilder {
private String x = "X";
private String y = "Y";
private String z = "Z";
}
}
Then the default value for x, y, z will be "X", "Y", "Z".
Another way to go is to use #Builder(toBuilder = true)
#Builder(toBuilder = true)
public class XYZ {
private String x = "X";
private String y = "Y";
private String z = "Z";
}
and then you use it as follows:
new XYZ().toBuilder().build();
With respect to the accepted answer, this approach is less sensible to class renaming. If you rename XYZ but forget to rename the inner static class XYZBuilder, then the magic is gone!
It's all up to to you to use the approach you like more.

Jackson2 and Lombok #Builder

Given I have the POJO:
import lombok.Builder;
import lombok.Data;
#Data
#Builder
public class SomeResponse {
private String author;
private String authorTitle;
private String teaser;
private String text;
private Long lastModified;
private Long created;
private Integer rating;
private Optional<Markdown> markdown;
private Optional<Integer> wordCount;
}
When I try to use the POJO in such normal Jackson construction:
restTemplate.getForObject(urlTemplate, SomeResponse.class,
productId.toString(), siteId.toString());
I get an exception, because there are private ctor in the SomeResponse class due to Lombok #Builder annotation.
How can I make it works without deleting Lombok #Builder annotation?
Also add #AllArgsConstructor and #NoArgsConstructor, possible with the right access values. See the documentation for appropriate parameters.
Disclosure: I am a lombok developer.

Java Lombok: Omitting one field in #AllArgsConstructor?

If I specify #AllArgsConstructor using Lombok, it will generate a constructor for setting all the declared (not final, not static) fields.
Is it possible to omit some field and this leave generated constructor for all other fields?
No that is not possible. There is a feature request to create a #SomeArgsConstructor where you can specify a list of involved fields.
Full disclosure: I am one of the core Project Lombok developers.
Alternatively, you could use #RequiredArgsConstructor. This adds a constructor for all fields that are either #NonNull or final.
See the documentation
Just in case it helps, initialized final fields are excluded.
#AllArgsConstructor
class SomeClass {
final String s;
final int i;
final List<String> list = new ArrayList<>(); // excluded in constructor
}
var x = new SomeClass("hello", 1);
It makes sense especially for collections, or other mutable classes.
This solution can be used together with the other solution here, about using #RequiredArgsConstructor:
#RequiredArgsConstructor
class SomeClass2 {
final String s;
int i; // excluded because it's not final
final List<String> list = new ArrayList<>(); // excluded because it's initialized
}
var x = new SomeClass2("hello");
A good way to go around it in some cases would be to use the #Builder
This can be done using two annotations from lombok #RequiredArgsConstructor and #NonNull.
Please find the example as follows
package com.ss.model;
import lombok.*;
#Getter
#Setter
#RequiredArgsConstructor
#ToString
public class Employee {
private int id;
#NonNull
private String firstName;
#NonNull
private String lastName;
#NonNull
private int age;
#NonNull
private String address;
}
And then you can create an object as below
Employee employee = new Employee("FirstName", "LastName", 27, "Address");
Lombok is meant to take care of the boilerplate code for your POJOs. Customized constructors/setters/getters/toString/copy etc are not on the boilerplate side of code. For these cases, every Java IDE provide easy to use code generators to help you do things in no time.
In your case a
public MyClass(String firstName, String lastName) {....}
is much more readable and makes more sense than a hypothetic:
#AllArgsConstructor(exclude = "id", exclude = "phone")
Have fun!

Categories