Lombok's builder with mandatory parameters - java

If I add #Builder to a class. The builder method is created.
Person.builder().name("john").surname("Smith").build();
I have a requirement where a particular field is mandatory. In this case, the name field is mandatory. Ideally, I would like to declare it like so.
Person.builder("john").surname("Smith").build();
When googling i found many alternatives like overriding the builder implementation as below:
#Builder
public class Person {
private String name;
private String surname;
public static PersonBuilder builder(String name) {
return new PersonBuilder().name(name);
}
}
And then use it like below:
Person p = Person.builder("Name").surname("Surname").build();
The problem with above approach is that it still provides the name() and PersonBuilder() method like below, which i don't want:
Person p = Person.builder("Name").surname("Surname").name("").build();
Person p = new Person.PersonBuilder().build;
Another approach is to add #lombok.nonnull check at name which will force to provide value for name while creating object. but it is a runtime check. it will not force me to provide value for name while creating object.
Is there any additional technique which lombok provides to achieve below:
Person p = Person.builder("Name").surname("Surname").build();
Note: The builder() and name() should not be exposed. The only way to create Person object should be either above or below:
Person p = Person.builder("Name").build();

You can't really do it with lombok, see the explanation from the library authors. But is it that complicated to roll this builder on your own?
public static class PersonBuilder {
private final String name;
private String surname;
PersonBuilder(String name) {
this.name = name;
}
public PersonBuilder surname(String surname) {
this.surname = surname;
return this;
}
public Person build() {
return new Person(name, surname);
}
}
with the same method that you already have:
public static PersonBuilder builder(String name) {
return new PersonBuilder(name);
}

Try to make the builder private.
Did you check this comment Required arguments with a Lombok #Builder
I am pretty sure you will find out once read the thread one more time.
P.S. If you have a class with only two field better use directly a constructor.

Best Practice:
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(name);
}
}
private static class PersonExtraBuilder extends PersonBuilder{
#Deprecated
#Override
public PersonBuilder name(String name) {
return this;
}
}
public static PersonNameBuilder builder(String name) {
return new PersonNameBuilder();
}
private static PersonExtraBuilder privateBuilder(){
return new PersonExtraBuilder();
}
}
Usage:
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 last `.name("")` will not work, and it will be marked as Deprecated by IDE.
Person p3 = Person.builder().name("John").surname("Smith").name("").build();

Related

How to use parent class default values in child class builder

I am having two classes:
1]BaseCustomer.java
#NoArgsConstructor
#AllArgsConstructor
#Getter
#Builder(builderMethodName="BaseBuilder")
public class BaseCusmtomer {
private String cutomerId;
private String age;
#Default
private Boolean isActive= true;
#Default
private String type = "XYZ";
}
2] Customer.java
#Builder
public class Customer extends BaseCustomer{
private Customer(String cutomerId, String age, Boolean isActive, String type){
super(customerId,age,isActive,type);
}
}
3]Test Object
Customer.builder().cutomerId("1").age("23").build();
ut while creating object using Customer builder it always take values of isActive and type as null, it should take default values from superclass. Is there anyway to do this?
Tried to call Child builder with default parent class values
but getting null values instead of default value.
Note: can't use Superbuilder as it is experimental feature.
Since using #SuperBuilder is not an option for you, there is not much to do. One option is to create BaseCustomer "copy" constructor and create Customer by passing BaseCustomer to copy . Like this:
#NoArgsConstructor
#AllArgsConstructor
#Getter
#Builder(builderMethodName = "BaseBuilder")
public class BaseCustomer {
protected String customerId;
protected String age;
#Default
protected Boolean isActive = true;
#Default
protected String type = "XYZ";
public BaseCustomer(BaseCustomer base) {
this.customerId = base.customerId;
this.age = base.getAge();
this.isActive = base.isActive;
this.type = base.type;
}
}
#Data
public class Customer extends BaseCustomer {
String name;
#Builder
private Customer(BaseCustomer base, String name) {
super(base);
this.name = name;
}
}
So as can be seen above, I marked Customer constructor with BaseCustomer parameter as #Builder. I added new parameter to Customer to see how it will work with additional fields. Now we can create Customer by building BaseCustomer first and then pass it with additional fields. For example:
Customer c2 = Customer.builder().base(BaseCustomer.BaseBuilder().customerId("1").age("23").build()).name("Name").build();
System.out.println(c2.getAge());
System.out.println(c2.getCustomerId());
System.out.println(c2.getType());
System.out.println(c2.getIsActive());
System.out.println(c2.getName());
This will print:
23
1
XYZ
true
Name
This has some advantages - you only pass single parameter (for base class) to Customer constructor and Customer constructor does not have to be changed for BaseCustomer field changes.

Update an immutable object with Lombok in Java?

I have a domain class Person annotated with Lombok #Value thus marking it as immutable, having has 3 fields.
In my service layer, I am making a call to the repository to check if the the person exists or not.
If it does exist, I need to take the Person object from the database and update the money field.
Since it is immutable, this cannot be done. I was reading some articles and came across that this can be done using builder pattern.
I will probably need to create a updatePerson() in my Person class but not sure how to do it. Or do I need to do something else ?
Person.java:
#Value
#Builder
public class Person {
private final UUID id;
private final String job;
private final BigDecimal money;
}
I am using Java 15.
You can also use another feature of lombok, which doesn't require you to use a builder. It's called #With and using this annotation will create immutable setters, meaning that the setter returns a new object with the attributes of the old one except for the attribute that you wanted to change.
#Value
public class Person {
/* You don't need to write final if you are using #Value. Lombok will make the variables final for you.
In theory you do not even need to write private,
because Lombok makes variables private by default instead of package private.*/
private UUID id;
private String job;
#With
private BigDecimal money;
}
Person newPerson = person.withMoney(new Big decimal("10"));
In general I'm not sure if making the object immutable is really a good idea. Every variable except UUID seems like it could change in the future.
Using Lombok:
#Value
#Builder(toBuilder = true)
public class Person {
private final UUID id;
private final String job;
private final BigDecimal money;
}
personObjectFromDatabase.toBuilder().setMoney(...).build()
OR
You can use the Builder pattern in that case:
public class Person {
private final UUID id;
private final String job;
private final BigDecimal money;
public static class PersonBuilder {
private UUID id;
private String job;
private BigDecimal money;
public PersonBuilder(Person defaultPerson){
this.id = defaultPerson.getId();
this.job = defaultPerson.getJob();
this.money = defaultPerson.getMoney();
}
public PersonBuilder withId(UUID id) {
this.id = UUID;
return this;
}
public PersonBuilder withJob(String job) {
this.job = job;
return this;
}
public PersonBuilder withMoney(BigDecimal money) {
this.money = money;
return this;
}
public Person build() {
return new Person(id, job, money);
}
}
}
Use this builder like the following:
Person person = new Person.PersonBuilder(personObjectFromDatabase)
.withMoney(...)
.build();
OR
You can just create a copyWith() method:
public class Person {
...
public Person copyWith(BigDecimal money) {
return new Person(this.id, this.job, money);
}
}
The class is immutable;
you can never change the values of an instance of that class.
Instead,
you must create a new instance of the class.
Do not write a builder;
you are already using Lombok,
just use the
#Builder
annotation and Lombok will create a builder for you.
Edit: You are using the builder annotation.
The soltion you are looking for appears to be this:
you must create a new instance of the class.

Mockito: how to mock an object that has certain property value

Assume we have the following class:
class Person {
private int age;
private String name;
public Person(int age, String name){
this.age = age;
this.name = name;
}
// getters and setters
}
and we also have some class:
class SpecialClass {
public int giveNumber(Person p) {
...
return (int)(...)
}
}
Assume I want to mock an object of SpecialClass that if 'giveNumber' is invoked with a Person object that has name property equals to 'John', then 'giveNumber' will retrieve 500.
For example,
SpecialClass sc = mock(SpecialClass.class);
when(sc.giveNumber(p with name = "John").thenReturn(500);
Is there any way to do it with Mockito?
You can use org.mockito.ArgumentMatchers.argThat(...) passing it a lambda that matches the desired instance. In this case the lamdba would be something like
(person) -> "John".equals(person.getName())
Putting it together:
SpecialClass sc = mock(SpecialClass.class);
when(sc.giveNumber(argThat((person) -> "John".equals(person.getName())))).thenReturn(500);

Generics in POJO - Is this a good practice

I have a Base Class.
#Data
class BaseDocument{
String id;
String name;
//Other fields
}
Say I have many classes that extends BaseDocument one below.
class NoteDocument extends BaseDocument{
String description;
Long lastModifiedDate;
//etc
}
It does not make sense to me to send entire document to UI in some cases. Most of the cases I need only id and name.
So for every document I have a VO class.
#Data
class BaseVO {
private String id;
private String name;
}
#Data
class NoteVO extends BaseVO{
//Nothing here now
}
And in NoteDocument I have.
public NoteVO getVo(){
Assert.notNull(getId());
NoteVO noteVo = new NoteVO();
noteVo.setName(getName());
noteVo.setId(getId());
return noteVo;
}
Now I have to copy this method in all the classes that extends BaseDocument.
Instead, I changed my BaseDocument like below.
#Data
class BaseDocument<V extends BaseVO>{
String id;
String name;
public V getVo(Class className) {
Assert.notNull(getId());
V vo = null;
try {
vo = (V) className.newInstance();
vo.setName(getName());
vo.setId(getId());
} catch (IllegalAccessException|InstantiationException e){
e.printStackTrace();
}
Assert.notNull(vo);
return vo;
}
}
I am new to generics. My first question, is this a good practice. Are there any problems in using reflection to create instance, any performance issues? Is there any better way to do achieve (write less code) this.
Edit: Suppose I need to display note in UI, Along with note I need to display name of the User who created note. I am using mongodb, when I save the note I also save UserVO in note, which will have user id and name of the user. If I save only user id while saving the note, I will have to do one more query to get the name of user while displaying. I want to avoid this.
Do not use reflection; use inheritance and maybe covariant return types instead. It will be faster, clearer, more precise, and easier to maintain. You may also find it useful to add methods to populate your VOs incrementally. I didn't come up with a clean way to apply generics to this situation, but I don't think you need them:
class BaseVO {
String id;
String name;
void setId(String id) {
this.id = id;
}
void setName(String name) {
this.name = name;
}
}
class NoteVO extends BaseVO {
// ...
}
#Data
class BaseDocument {
String id;
String name;
//Other fields
protected void populateBaseVO(BaseVO vo) {
vo.setId(id);
vo.setName(name);
}
public BaseVO getVO() {
BaseVO vo = new BaseVO();
populateBaseVO(vo);
return vo;
}
}
#Data
class NoteDocument extends BaseDocument {
String description;
Long lastModifiedDate;
// ....
protected void populateNoteVO(NoteVO vo) {
populateBaseVO(vo);
// ...
}
public NoteVO getVO() {
NoteVO vo = new NoteVO();
populateNoteVO(vo);
return vo;
}
}

'dynamic'-like java annotations?

I have a pojo that is dependent on annotations. It has predefined fields as well as a Set that contains user provided fields:
public class MyPOJO implements Document {
private String id;
private LocalString name;
private LocalString desc;
private List<Field> fields;
public MyPOJO(final String id,
final LocalString name,
final LocalString desc,
final List<Field> fields) {
this.id = id;
this.name = name;
this.desc = desc;
this.fields = fields;
}
public String getId() {
return id;
}
#Indexed(searchable = false, stored = true)
public LocalString getName() {
return name;
}
#Indexed(searchable = true)
public LocalString getDescription() {
return desc;
}
public List<Field> getFields() {
return fields;
}
}
MyPOJO is a 'generic' object, ie, the developer (or consumer) of MyPOJO has fields that are not predefined in MyPOJO and therefore the developer needs to place these additional fields the in attribute 'fields'. The problem arises from the fact that each object in the Set fields needs to have its own annotations to indicate whether the particular field is either stored or searchable in order to remain consistent with the predefined attributes, such as name.
I can think of two options:
For each additional field, the developer will have to create an
anonymous class implementing the interface Field and inside this
anonymous class, the developer will declare the applicable
annotations.
the Set 'fields' contains a complex object of fieldname, fieldvalue
and annotations as shown below. I can't figure out how to invoke the constructor for Field. The below code does not compile but it is intended as pseudo-code to signify what I am trying to do.
Field myfield1 = new Field("dateofBirth", new Date(), new ArrayList({Index.stored, Index.searchable});
Field myfield2 = new Field("model", "330i", new ArrayList({Index.stored});
There is no construct to pass annotations as a parameter: new ArrayList({Index.stored}.
public class Field {
private String name;
private Object value;
Collection<Annotation> annotations;
public Field(final String name, final Object value, Collection<Annotation> annotations;) {
this.name = name;
this.value = value;
this.annotations = Collections.unmodifiableCollection(annotations);
}
public String getName() {
return name;
}
public Object getValue() {
return value;
}
}
I'm not particularly excited with either option and hoping someone can give me some pointers
If you need an extensible object model, I'd say a POJO design is just setting yourself up for extra work as opposed to exposing a metamodel.
That said, what you could do is have clients of the API subclass MyPOJO, and annotate the properties they define in their subclasses. You would then use reflection to go through all JavaBeans properties of the objects you're receiving and determine the annotations on the getters - similarly to how JPA works.

Categories

Resources