Streamlining Java Development with Lombok Annotations
— java — 7 min read
Lombok is a Java library that simplifies the process of writing Java code by reducing boilerplate code, such as getters, setters, constructors, and more. Lombok helps to improve productivity and code readability by providing annotations that generate code during compilation time. In this post, we will explore the basics of Lombok, how to install it, and some of the most commonly used Lombok annotations.
Installing Lombok
Before we dive into the different annotations Lombok offers, let's first look at how to install Lombok into your Java project.
The easiest way to install Lombok is by using a dependency management tool such as Maven or Gradle. To use Lombok with Maven, add the following dependency to your pom.xml
file:
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> <scope>provided</scope></dependency>
To use Lombok with Gradle, add the following dependency to your build.gradle
file:
compileOnly 'org.projectlombok:lombok:1.18.20'annotationProcessor 'org.projectlombok:lombok:1.18.20'
Common Lombok Annotations
@Getter and @Setter
The @Getter
and @Setter
annotations generate getter and setter methods for fields in your Java classes, eliminating the need to write them manually. For example:
@Getter@Setterpublic class Person { private String firstName; private String lastName;}
This generates the following code during compilation time:
public class Person { private String firstName; private String lastName;
public String getFirstName() { return firstName; }
public void setFirstName(String firstName) { this.firstName = firstName; }
public String getLastName() { return lastName; }
public void setLastName(String lastName) { this.lastName = lastName; }}
@NoArgsConstructor and @AllArgsConstructor
The @NoArgsConstructor
and @AllArgsConstructor
annotations generate constructors with no arguments and all arguments, respectively. For example:
@NoArgsConstructor@AllArgsConstructorpublic class Person { private String firstName; private String lastName; private int age;}
This generates the following code during compilation time:
public class Person { private String firstName; private String lastName; private int age;
public Person() { }
public Person(String firstName, String lastName, int age) { this.firstName = firstName; this.lastName = lastName; this.age = age; }}
@NonNull
The @NonNull
annotation is used to add null-checks to method or constructor parameters, generating a NullPointerException if a null value is passed. This annotation can be useful in preventing null pointer exceptions in your code.
Here is an example of how to use the @NonNull
annotation:
public class Person { private final String name; public Person(@NonNull String name) { this.name = name; } public String getName() { return this.name; }}
In the example above, the @NonNull
annotation is used on the name
parameter of the Person
constructor. This ensures that a NullPointerException
will be thrown if null
is passed as the name
parameter.
During compilation, Lombok generates the following code for the Person
class:
public class Person { private final String name; public Person(@NonNull String name) { Objects.requireNonNull(name, "name is marked @NonNull but is null"); this.name = name; } public String getName() { return this.name; }}
As you can see, Lombok generates the null-check for the name
parameter using the Objects.requireNonNull
method.
@EqualsAndHashCode
The @EqualsAndHashCode
annotation generates equals
and hashCode
methods for the class based on the specified fields. This annotation can be useful in simplifying the code for checking equality between objects.
Here is an example of how to use the @EqualsAndHashCode
annotation:
@EqualsAndHashCodepublic class Person { private final String name; private final int age;}
In the example above, the @EqualsAndHashCode
annotation is used on the Person
class. This generates equals
and hashCode
methods for the class based on the name
and age
fields.
During compilation, Lombok generates the following code for the Person
class:
public class Person { private final String name; private final int age; public boolean equals(final Object o) { if (o == this) { return true; } if (!(o instanceof Person)) { return false; } final Person other = (Person) o; if (!other.canEqual((Object) this)) { return false; } final Object this$name = this.name; final Object other$name = other.name; if (this$name == null ? other$name != null : !this$name.equals(other$name)) { return false; } if (this.age != other.age) { return false; } return true; } protected boolean canEqual(final Object other) { return other instanceof Person; } public int hashCode() { final int PRIME = 59; int result = 1; final Object $name = this.name; result = result * PRIME + ($name == null ? 43 : $name.hashCode()); result = result * PRIME + this.age; return result; }}
As you can see, the generated equals
method checks for equality of the name
and age
fields, and the hashCode
method generates a hash code based on the same fields.
@Builder
The @Builder
annotation generates a builder pattern for a class, allowing for easy creation of complex objects with many fields. The builder pattern is a creational design pattern that separates the construction of a complex object from its representation, allowing for more flexible and readable code. With the @Builder
annotation, we can generate a builder class that can be used to set values for each field in a fluent and easy-to-read way.
Let's consider a simple example. Suppose we have a class called Person
with three fields: name
, age
, and email
.
@Builderpublic class Person { private String name; private int age; private String email;}
Here, we've annotated our class with @Builder
. This generates a builder class that we can use to create instances of Person. We can now create a new instance of Person using the builder as follows:
Person person = Person.builder() .name("John") .age(30) .email("john@example.com") .build();
In this example, we're using the builder to set the values of each field. The builder returns a Person object with the specified values.
Here is the compilation output of the Person
class:
public class Person { private String name; private int age; private String email;
Person(String name, int age, String email) { this.name = name; this.age = age; this.email = email; }
public static PersonBuilder builder() { return new PersonBuilder(); }
public String getName() { return this.name; }
public int getAge() { return this.age; }
public String getEmail() { return this.email; }
public static class PersonBuilder { private String name; private int age; private String email;
PersonBuilder() { }
public Person.PersonBuilder name(String name) { this.name = name; return this; }
public Person.PersonBuilder age(int age) { this.age = age; return this; }
public Person.PersonBuilder email(String email) { this.email = email; return this; }
public Person build() { return new Person(this.name, this.age, this.email); }
public String toString() { return "Person.PersonBuilder(name=" + this.name + ", age=" + this.age + ", email=" + this.email + ")"; } }}
@Data
The @Data
annotation generates all the getters, setters, equals, hashCode, and toString methods for the class. This can reduce boilerplate code in classes that require these methods.
@Datapublic class Person { private String name; private int age;}
When the @Data
annotation is applied to the Person class, Lombok generates the following code during compilation:
public class Person { private String name; private int age;
public String getName() { return this.name; }
public void setName(String name) { this.name = name; }
public int getAge() { return this.age; }
public void setAge(int age) { this.age = age; }
@Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; return age == person.age && Objects.equals(name, person.name); }
@Override public int hashCode() { return Objects.hash(name, age); }
@Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; }}
Final Thoughts
The annotations discussed in this article are just a few examples of the many annotations available in Lombok that can help simplify the code. Please visit the Lombok documentation to discover more useful annotations!
By using Lombok, developers can focus more on writing the logic of their application rather than worrying about the repetitive and tedious aspects of Java development. Overall, Lombok is a valuable addition to any Java developer's toolkit and can greatly improve the efficiency and readability of their code.