In this blog post, we share with you some benefits from using Lombok - a Java library that helps to decrease a boilerplate. You can see, how to use Lombok and discover some particular details you may don’t know yet. Also, you will find out if it’s possible to use this library with other languages.
Lombok is a Java library that “spices up” your code as you can read in its official website. In fact, it’s a collection of very useful annotations that allows you to forget about implementing e.g. your own constructors, setters, getters and toString or equalsAndHashCode methods. What is important, this lib has over 7 thousand stars on GitHub, so as you can see it could be a pretty reliable tool for your project.
Let’s start with these 3 annotations which speak for themselves. Here is an example of how to use them:
import java.math.BigDecimal;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@AllArgsConstructor
class Bicycle {
private String name;
private BigDecimal value;
private Wheel wheel;
}
And here’s an example of generated Bicycle
class with these annotations after build:
import java.math.BigDecimal;
class Bicycle {
private String name;
private BigDecimal value;
private Wheel wheel;
public String getName() {
return this.name;
}
public BigDecimal getValue() {
return this.value;
}
public Wheel getWheel() {
return this.wheel;
}
public void setName(String name) {
this.name = name;
}
public void setValue(BigDecimal value) {
this.value = value;
}
public void setWheel(Wheel wheel) {
this.wheel = wheel;
}
public Bicycle(String name, BigDecimal value, Wheel wheel) {
this.name = name;
this.value = value;
this.wheel = wheel;
}
}
As you can see in the example, it provides you all getters, setters and also a constructor which requires all class’ fields as arguments. Ok, how about a constructor with no arguments? Here you go:
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Setter
@NoArgsConstructor
@EqualsAndHashCode(of = "size")
class Wheel {
private int size;
private String type;
}
The above example shows also how to use @EqualsAndHashCode
annotation. Thanks to that, non-primitive objects can be compared over their fields. I wanted my Wheel
class to be comparable only by its “size” field. Anyway, look at the generated class:
class Wheel {
private int size;
private String type;
public void setSize(int size) {
this.size = size;
}
public void setType(String type) {
this.type = type;
}
public Wheel() {
}
public boolean equals(Object o) {
if (o == this) {
return true;
} else if (!(o instanceof Wheel)) {
return false;
} else {
Wheel other = (Wheel)o;
if (!other.canEqual(this)) {
return false;
} else {
return this.size == other.size;
}
}
}
protected boolean canEqual(Object other) {
return other instanceof Wheel;
}
public int hashCode() {
int PRIME = true;
int result = 1;
int result = result * 59 + this.size;
return result;
}
}
In effect, Lombok provided a no-argument constructor and some helpful methods as I mentioned before like hashCode()
and equals()
. They check in their last condition if the objects have the same size fields.
Another interesting annotation is definitely the @Builder
. It provides a builder for a class which is an attractive alternative for creating a class. Let’s say we have a Rider
class like below. It also presents the usage of @NonNull
annotation.
import lombok.Builder;
import lombok.Getter;
import lombok.NonNull;
@Getter
@Builder
class Rider {
private String firstName;
private String secondName;
private Bicycle ownedBicycle;
public void changeWheel(@NonNull final Wheel wheel) {
if (!ownedBicycle.getWheel().equals(wheel)) {
ownedBicycle.setWheel(wheel);
}
}
}
As a result, two simple annotations provide a number of methods which you can see below:
import lombok.NonNull;
class Rider {
private String firstName;
private String secondName;
private Bicycle ownedBicycle;
public void changeWheel(@NonNull Wheel wheel) {
if (wheel == null) {
throw new NullPointerException("wheel is marked @NonNull but is null");
} else {
if (!this.ownedBicycle.getWheel().equals(wheel)) {
this.ownedBicycle.setWheel(wheel);
}
}
}
Rider(String firstName, String secondName, Bicycle ownedBicycle) {
this.firstName = firstName;
this.secondName = secondName;
this.ownedBicycle = ownedBicycle;
}
public static Rider.RiderBuilder builder() {
return new Rider.RiderBuilder();
}
public String getFirstName() {
return this.firstName;
}
public String getSecondName() {
return this.secondName;
}
public Bicycle getOwnedBicycle() {
return this.ownedBicycle;
}
public static class RiderBuilder {
private String firstName;
private String secondName;
private Bicycle ownedBicycle;
RiderBuilder() {
}
public Rider.RiderBuilder firstName(String firstName) {
this.firstName = firstName;
return this;
}
public Rider.RiderBuilder secondName(String secondName) {
this.secondName = secondName;
return this;
}
public Rider.RiderBuilder ownedBicycle(Bicycle ownedBicycle) {
this.ownedBicycle = ownedBicycle;
return this;
}
public Rider build() {
return new Rider(this.firstName, this.secondName, this.ownedBicycle);
}
public String toString() {
return "Rider.RiderBuilder(firstName=" + this.firstName + ", secondName=" + this.secondName + ", ownedBicycle=" + this.ownedBicycle + ")";
}
}
}
As a result, Lombok created a static subclass RiderBuilder
which has setters
, no-args constructor
, build()
and a nice toString()
method. Example how you can use the builder is below, but look again how @NonNull
annotation changed the changeWheel()
method with an annotated wheel
parameter. It throws a NullPointerException
with a message if the provided parameter is null.
Ok. The last step in this post we have to make is to check how we can use mentioned features, that these annotations provide, in practice. To demonstrate, I’ve created a Main
class which uses all created before objects in its main()
method.
import java.math.BigDecimal;
public class Main {
public static void main(String[] args) {
final Wheel smallWheel = new Wheel();
smallWheel.setSize(22);
smallWheel.setType("aluminium");
final Wheel bigWheel = new Wheel();
bigWheel.setSize(24);
bigWheel.setType("carbon");
final Bicycle bicycle = new Bicycle("Bianchi", new BigDecimal("10000.0"), smallWheel);
final Rider rider = Rider.builder()
.firstName("Alejandro")
.secondName("Valverde")
.ownedBicycle(bicycle)
.build();
rider.changeWheel(bigWheel);
}
}
As can be seen, we have two Wheel
objects. In the beginning, they’re created as an empty object, thanks to the @NoArgsConstructor
. What’s more, their size
and type
could be set later thanks to the @Setter
annotation. Another Bicycle
class is created with all arguments put in its constructor (@AllArgsContructor
). The last object is created by the builder. The @Builder
has provided a static builder()
method which creates an empty RiderBuidler class. Thanks to that we’re able to use generated setter methods on its class. The last step to do is to call build()
method which gathers all RiderBuilder
fields, creates the Rider
class using these fields in a constructor and then returns it.
It’s worth to mention that since it’s a library written in Java, you could think that you can use it in any projects that use other JVM languages like Kotlin, Scala or Groovy. You can try and it’s possible that you’ll manage to do that in some cases, but I don’t recommend it. This lib gives some features for Java that many other languages, as mentioned above, already have. For example, Kotlin has it’s data
class, Scala has case
class and Groovy an annotation @Canonical
. What is more, you might also have some problems with migrating Java project which uses Lombok to other JVM language or just with starting a new project in JVM language other than Java. Personally, I’ve never tried to do that, but there are many reports on the Internet from developers about problems during their attempts to integrate Lombok in their projects. Why? Here is an example: Kotlin uses javac like Java, but without annotation processing which is crucial for making a Lombok working. It’s because this lib is an annotation processor which means that it generates classes annotated with its features during the build of a project. So in result, Kotlin is not able to know what these annotations are before compiling. So, as you can see, trying might be a bit risky.
So, as you can see if you are planning to stick to Java then this library is a really good choice for you. Thanks to that you’ll boost your Java code and reduce an evil boilerplate. Well, at least partially.