Recently we started make use of Lombok features in our project. we have #Data annotation for the Domain object, due to this running with some exception thrown by hashCode() method provided by Lombok api. Later, when I added #Setter and #Getter instead of #Data, I didn't see any issues.
Question1: Does Lombok #Data override the existing methods in a class like hashCode() and toString()?
Question2: why is hashCode() making problems here?
Yes, #Data implies #EqualsAndHashCode and #ToString. See the #Data documentation.
The generated hashCode() method will call the hashCode methods for all fields. So if one of the values of the fields throws an exception, so will this.
One other scenario is that you have circular object references: If one object has a field that contains an object that has a field that refers to the first object, invoking the hashCode method will trigger a StackOverflow.
Disclosure: I am one of the Lombok developers.
Related
I have a Person object which has 20 fields and stored in dynamoDB.
I want to create a new Person object based on some input, check if the same object exists in the Database or not. If it exists, I want to compare the 2 objects on the basis of 19 fields out of 20. The field to ignore is a boolean flag check.
I am using Lombok #Data to generate the equals method.
Is there a way to do this without having to write a full fledged overriden equals method myself ?
You can also use Lombok's #EqualsAndHashCode in conjunction with an exclude.
#EqualsAndHashCode
public class MyObject{
#EqualsAndHashCode.Exclude
Object fieldToExclude;
}
Instead of choosing which to exclude you can also opt to choose the ones you wish to include like so. All which is not annotated with Include will not be used in the equals & hashcode implementation.
#EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class MyObject{
#EqualsAndHashCode.Include
Object fieldToInclude;
}
Lombok EqualsAndHashCode documentation
In many sites, I saw lombok #Builder can be used to create immutable objects here(https://www.baeldung.com/lombok-builder-singular) and also many sites say Builder pattern is mainly used to create immutable objects.
TimeIntervalData td = TimeIntervalData.builder().endTime("12:00").startTime("10:00").build();
td.setEndTime("14:00");
System.out.println(td.getEndTime());
I am not sure how I can use setters on objects built using builders. Is there something I am missing here?
Yes lombok builder will not create immutable instances util user define the parameters in class as final, As per the docs from lombok.builder #Builder lets you automatically produce the code required to have your class be instantiable with code such as:
Person.builder().name("Adam Savage").city("San Francisco").job("Mythbusters").job("Unchained Reaction").build();
As per the docs it just creates inner static class with the same properties in the same way that mentioned in outer class
A method annotated with #Builder (from now on called the target) causes the following 7 things to be generated:
An inner static class named FooBuilder, with the same type arguments as the static method (called the builder).
In the builder: One private non-static non-final field for each parameter of the target.
In the builder: A package private no-args empty constructor.
In the builder: A 'setter'-like method for each parameter of the target: It has the same type as that parameter and the same name. It returns the builder itself, so that the setter calls can be chained
In the builder: A build() method which calls the method, passing in each field. It returns the same type that the target returns
In the builder: A sensible toString() implementation.
In the class containing the target: A builder() method, which creates a new instance of the builder.
But using #Singular with #Builder annotation for collection properties makes them singleton
By annotating one of the parameters (if annotating a method or constructor with #Builder) or fields (if annotating a class with #Builder) with the #Singular annotation, lombok will treat that builder node as a collection, and it generates 2 'adder' methods instead of a 'setter' method. One which adds a single element to the collection, and one which adds all elements of another collection to the collection. No setter to just set the collection (replacing whatever was already added) will be generated.
#Singular can only be applied to collection types known to lombok.
is mainly used to create immutable objects
This statement is not correct. The main purpose is to reduce boiler plate code. Creation of object using builder is compacter, it is easier to write and is easier to read.
When you have 1-2 attributes, builder can be disadvantage and can make the code less readable compared to passing parameters via constructor.
So when to use or not to use builder is a matter of taste.
To setters: If you need setters, you can generate them by #Data annotation.
#Data
public abstract class B {
private final String str;
}
#Data
public class A extends B{
private final String s;
}
Data on class A complains in intellij, but the codes can get compiled successfully through command line, not sure what to do
One problem is that #Data is meant for mutable data and there's nothing mutable in your classes. So using #Data is simply wrong... and whether it compiles or not doesn't really matter.
If you want mutable data, then remove the final field. For immutable data, make all fields final and use #Value. Sometimes, partially mutable data is needed, but I try hard to avoid it as it's confusing (some fields can be set, some can't) and they provide disadvantages of both.
The other problem is that Lombok can't access class hierarchies. With B having a final field, you need it to be initialized in the constructor, which means that A's constructor has to call a non-default constructor. This isn't possible with Lombok. There's #Superbuilder in Lombok, which is about the only feature of Lombok dealing well with class hierarchies.
The #Data annotation does not add a default contructor.
Try to add a #NoArgsConstructor to your Base Class B to generate a default constructor with Lombok.
You can also read up what #Data actually means here.
This does not event compile. In Intellij, when you are not sure what is the problem with lombok code, you can open class in which you are unsure, go on Refactor -> Delombok -> All lombok annotations and you will see what lombok actually create for you.
After scouring the Lombok feature-list and in particular the documentation for the Getter/Setter and #Value annotations I have not been able to find any setting that suppresses the code generated by #Getter.
In practice, #Value is shorthand for: final #ToString #EqualsAndHashCode #AllArgsConstructor #FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) #Getter
This is important as I do not want to leak references to objects that are themselves mutable.
Effective Java references this type of issue in "Item 39: Make defensive copies when needed". It seems that #Wither could partly solve this issue by making actual defensive copies but I want to avoid leaking attributes what so ever, regardless of them being mutable.
While it is possible to roll one's own #Value annotation that omits the #Getter I would, of course, prefer not to as that would add unwarranted complexity to the codebase if such a setting already exists.
You could use:
#Value
#Getter(AccessLevel.NONE)
AccessLevel.NONE instructs Lombok to not generate the getters. That's the best you can do right now.
I have several different POJOs that use a builder pattern, but after adding a builder for each one and generating Object.toString, Object.hashCode and Object.equals, my classes end up being around 100 lines of code. There has to be a better way to handle this. I think having some sort of a reflective builder would help out a lot, but I'm not sure this would be good practice and I'm also not sure how I'd exactly make it happen. In other words, is there a way to implement a builder like this?
A simple POJO:
public class Foo {
public int id;
public String title;
public boolean change;
...
}
Then some sort of reflective builder:
Foo = ReflectiveBuilder.from(Foo.class).id(1).title("title").change(false).build();
Short answer no. What you ask for is not possible. Reflection looks at the code at runtime and invokes methods dynamically, it cannot generate actual methods.
What you could do would be:
Foo foo = ReflectiveBuilder.from(Foo.class).
set("id", 1).
set("title", "title").
build();
This has three massive problems:
the fields are Strings - a typo causes a runtime error rather than a compile time one,
the values are Objects - the wrong type causes a runtime error rather than a compile time one, and
it would be much slower than the alternative as Reflection is very slow.
So a reflection based solution, whilst possible (see Apache Commons BeanUtils BeanMap) is not at all practical.
Long answer, if you're willing to allow some compile time magic, you can use Project Lombok. The idea behind Lombok is to generate boilerplate code from annotations using the Java annotation preprocessor system.
The really magical thing is that all IDEs, well the big 3 at least, understand annotation preprocessing and code completion will still function correctly even though the code doesn't really exist.
In the case of a POJO with a Builder you can use #Data and #Builder
#Data
#Builder
public class Foo {
public int id;
public String title;
public boolean change;
...
}
The #Data annotation will generate:
a required arguments constructor (that takes all final fields),
equals and hashCode methods that use all fields (can be configured with the #EqualsAndHashCode annotation)
a toString method on all fields (can be configured with the #ToString annotation and
public getters and setters for all fields (can be configured using the #Getter / #Setter annotations on fields).
The #Builder annotation will generate an inner class called Builder that can be instantiated using Foo.builder().
Do make sure you configure the equals, hashCode and toString methods as if you have two classes with Lombok that have references to each other then you will end up with an infinite loop in the default case as both classes include the other in these methods.
There is also a new configuration system that allows you to use, for example, fluent setters so you can more of less do away with the builder if your POJO is mutable:
new Foo().setId(3).setTitle("title)...
For another approach you can look at Aspect-oriented programming (AOP) and AspectJ. AOP allows you do chop your classes up into "aspects" and then stick them together using certain rules using a pre-compiler. For example you could implement exactly what Lombok does, using custom annotations and an aspect. This is a fairly advanced topic however, and might well be overkill.
Maybe Project Lombok (yes the website is ugly) is an option for you. Lombok injects code into your classes based on annotations.
With Lombok you use the #Data annotations to generated getters, setters, toString(), hashCode() and equals():
#Data
public class Foo {
public int id;
public String title;
public boolean change;
}
Have a look at the example on the #Data documentation section to see the generated code.
Lombok also provides a #Builder that generates a builder for your class. But be aware that this is an experimental feature:
#Builder
public class Foo {
public int id;
public String title;
public boolean change;
}
Now you can do:
Foo foo = Foo.builder()
.id(123)
.title("some title")
.change(true)
.build();
I personally use this website to create all the boilerplate code for the POJOs for me. All you need to do is to paste the JSON that you want to parse, and it will generate all the classes for you. Then I just use Retrofit to do the requests/caching/parsing of the information. Here is an example of Retrofit and POJOs in my Github account.
I hope it helps!
I created a small library CakeMold to do fluent initialization of POJOs. It uses reflection, what is certainly not fast. But can be very helpful when need to write tests.
Person person = CakeMold.of(Person.class)
.set("firstName", "Bob")
.set("lastName", "SquarePants")
.set("email", "sponge.bob#bikinibottom.io")
.set("age", 22)
.cook();