โ๏ธ Immutables/AutoValue/Lombok ๐ฅ Which One?
Blog post: https://dev.to/cchacin/immutables-autovalue-lombok-which-one-2j33
Posted on April 13, 2020
The article was initially published at carloschac.in
See also:
In this article, we are going to compare some of the features of the Immutables.org library, Google AutoValue and Project Lombok:
Builder
pattern by default?Optional
and List
?The three libraries are based on an annotation processor to generate/modify code for us:
equals
, hashCode
and toString
methodsNOTE: Immutables
and AutoValue
generate new classes with the processor, and Lombok
modifies the bytecode of the original class.
Immutables Java annotation processors to generate simple, safe, and consistent value objects. Do not repeat yourself, try Immutables, the most comprehensive tool in this field!
AutoValue provides an easier way to create immutable value classes, with a lot less code and less room for error, while not restricting your freedom to code almost any aspect of your class exactly the way you want it.
Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java.
Never write another getter or equals method again, with one annotation your class has a fully-featured builder, Automate your logging variables, and much more.
We are going to create a class using the three libraries to be able to create an object representation with the following fields.
Optional<Integer> myOptional;
String myString;
List<String> myList;
AutoValue
package autovalue;
import com.google.auto.value.AutoValue;
import java.util.List;
import java.util.Optional;
@AutoValue
public abstract class MyModel {
public abstract Optional<Integer> myOptional();
public abstract String myString();
public abstract List<String> myList();
// Builder not generated by default
// We have to write this boilerplate code
@AutoValue.Builder
public abstract static class Builder {
public abstract Builder setMyOptional(Optional<Integer> myOptional);
public abstract Builder setMyString(String myString);
public abstract Builder setMyList(List<String> myList);
public abstract MyModel build();
}
}
Builder
pattern by default? ๐ดOptional
and List
? ๐ด Let's see in the next sectionLombok
package lombok;
import java.util.List;
import java.util.Optional;
@Value
@Builder
public class MyModel {
Optional<Integer> myOptional;
String myString;
List<String> myList;
}
Builder
pattern by default? ๐ดOptional
and List
? ๐ด Let's see in the next sectionImmutables
package immutables;
import org.immutables.value.Value;
import java.util.List;
import java.util.Optional;
@Value.Immutable
public interface MyModel {
Optional<Integer> myOptional();
String myString();
List<String> myList();
}
Builder
pattern by default? โ
Optional
and List
? โ
, Let's see in the next sectionLet's check some Pseudo-code:
We are going to create two identical lists with the same element inside:
list1=List.of("OneValue")
list2=List.of("OneValue")
We are going to create two identical value objects like this:
MyModel1: (
myOptional=Optional.of(1)
myString="Hello"
myList=list1 // Using list 1
)
MyModel2: (
myOptional=Optional.of(1)
myString="Hello"
myList=list2 // Using list 2
)
Even when using different references for the lists, the objects should be equal by value.
model1 == model2 // TRUE
After mutating one of the lists, the comparison of the objects should be the same
list1.add("AnotherValue")
model1 == model2 // TRUE
AutoValue
@Test
void immutability() {
// Create 2 lists containing the same element
var myList1 = new ArrayList<String>();
myList1.add("OneValue");
var myList2 = List.of("OneValue");
// Create model 1, assigning the list1
var myModel1 = new AutoValue_MyModel.Builder()
.setMyOptional(Optional.of(1)) // ๐ฅ ๐ด No helper for Optional
.setMyString("Hello")
.setMyList(myList1) // ๐ฅ ๐ด No helper for List
.build();
// Create model 2, assigning the list2
var myModel2 = new AutoValue_MyModel.Builder() // ๐ฅ ๐ด No helper for copying
.setMyOptional(Optional.of(1))
.setMyString("Hello")
.setMyList(myList2)
.build();
// Compare the 2 objects
// Test passes since the fields contain the same values
assertThat(myModel1).isEqualTo(myModel2);
// Mutate the list used on Model 1
myList1.add("AnotherValue");
// Compare the 2 objects:
// - PASSES objects are NOT equal for AutoValue ๐ฎ ๐ด
assertThat(myModel1).isNotEqualTo(myModel2);
}
Lombok
@Test
void immutability() {
// Create a mutable list with 1 element
var myList1 = new ArrayList<String>();
myList1.add("OneValue");
var myList2 = List.of("OneValue");
// Create model 1, assigning the list1
var myModel1 = MyModel.builder()
.myOptional(Optional.of(1)) // ๐ฅ ๐ด No helper for Optional
.myString("Hello")
.myList(myList1) // ๐ฅ ๐ด No helper for List
.build();
// Create model 2, assigning the list2
var myModel2 = MyModel.builder() ๐ฅ ๐ด // No helper for copying
.myOptional(Optional.of(1))
.myString("Hello")
.myList(myList2)
.build();
// Compare the 2 objects
// Test passes since the fields contain the same values
assertThat(myModel1).isEqualTo(myModel2);
// Mutate the list used on Model 1
myList1.add("AnotherValue");
// Compare the 2 objects:
// - PASSES objects are NOT equal for Lombok ๐ฎ ๐ด
assertThat(myModel1).isNotEqualTo(myModel2);
}
Immutables
@Test
void immutability() {
// Create a mutable list with 1 element
var myList1 = new ArrayList<String>();
myList1.add("OneValue");
var myList2 = List.of("OneValue");
// Create model 1, assigning the list1
var myModel1 = ImmutableMyModel.builder()
.myOptional(1) // ๐ฉ โ
Helper for Optional
.myString("Hello")
.myList(myList1)
.build();
// Create model 2, assigning the list2
var myModel2 = ImmutableMyModel.builder()
.from(myModel1) // ๐ฉ โ
Helper for copying
.addMyList("OneValue") // ๐ฉ โ
Helper for List
.build();
// Compare the 2 objects
// Test passes since the fields contain the same values
assertThat(myModel1).isEqualTo(myModel2);
// Mutate the list used on Model 1
myList1.add("AnotherValue");
// Compare the 2 objects:
// - Test PASSES objects ARE EQUAL for Immutables ๐ฉ โ
assertThat(myModel1).isEqualTo(myModel2);
}
AutoValue | Lombok | Immutables | |
---|---|---|---|
Line of Code to write/maintain | 28 | 14 | 15 |
Builder (by default) | ๐ด | ๐ด | โ |
Required IDE Plugin | โ | ๐ด | โ |
Immutability | ๐ด | ๐ด | โ |
Helper for Optional | ๐ด | ๐ด | โ |
Helper for Collections | ๐ด | ๐ด | โ |
Helper for Copying | ๐ด | ๐ด | โ |
The code and the tests for the above examples are available on GitHub:
Blog post: https://dev.to/cchacin/immutables-autovalue-lombok-which-one-2j33
Even when the three libraries are doing a great job to avoid the boilerplate code, I personally use the Immutables
library in most of the projects because of the safe defaults.
To be fair, both Lombok
and AutoValue
can achieve also immutability but it requires paying more attention when creating the classes and that can cause problems.
One main advantage of AutoValue
is that it generates less code and that would be convenient if you are developing on/for Android
.
The configuration options for AutoValue
and Lombok
are pretty limited compared with Immutables
but that topic was not covered in this article.
Lombok requires a plugin if you want to be able to see all the modified/added methods to the bytecode.
Posted on April 13, 2020
Sign up to receive the latest update from our blog.