Java Unleashed: Getters, Constructors, Builders, and More
Automated Testing Lombok

Lombok Unleashed: Elevating Java Efficiency with Getters, Setters, Constructors, Builders, and More

Table of Contents

Java is one of the best languages when it comes to “Object-Oriented Programming”. But it also faces some major drawbacks, such as code complexity and overuse of the same codes repeatedly. This doesn’t add any value to our programs, and at the same time, it is very time-consuming for the coders too. So, to overcome this major drawback of Java, “Project Lombok” comes into play, providing Lombok builders and constructors to further simplify code. 

Introduction to Project Lombok

Project Lombok is a popular Java library that aims to reduce boilerplate code and enhance coders productivity by saving lots of time and their energy by providing annotations to automatically generate common Java code during compile time

What is Project Lombok ?

  • Project Lombok addresses the verbosity of Java by offering annotations that eliminate the need for manually writing repetitive code constructs such as getters, setters, constructors, equals, hashCode, and toString methods. By annotating fields or classes with Lombok annotations, coders can instruct the compiler to generate these methods automatically, reducing the amount of boilerplate code and making Java classes more compact and readable.

Why are we using Project Lombok ?

  • Using Project Lombok in Java offers several compelling benefits that contribute to improved productivity, code quality, and maintainability.
  • Here are a few reasons to choose Project Lombok.
    • It reduces the “Boilerplate Code”.
    • It also improves codes reusability and readability.
    • It is very simple to implement and doesn’t have any complexity.
    • Integrates easily with “IDEs”.

How to implement Lombok in Java on a Maven project

Most of our projects are based on Maven. So, we just have to add “Project Lombok” dependencies to our “Pom.xml” file present in our project.

Go to maven repository and copy Lombok Maven repository from there, add the latest lombok dependency in your “Pom.xml” and save it, then refresh the project.

<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
   <dependency>
       <groupId>org.projectlombok</groupId>
       <artifactId>lombok</artifactId>
       <version>1.18.30</version>
       <scope>provided</scope>
   </dependency>
</dependencies>

Getters, Setters feature of Project Lombok in Java

In Java, by far the most common practice is to add getters and setters using the “Java Beans” pattern. Most of the IDEs automatically generate code for these patterns.

Let us see the code understand this approach by creating getter and setter with the help of “Data Objects” and “Data Factory” :

Data Object without Lombok

package org.example.dataobjects;

public class Profile {

   private String firstName;
   private String lastName;
   private String designation;
   private int age;

   // Getter method for 'firstName' field
   public String getFirstName() {
       return firstName;
   }

   // Setter method for 'firstName' field
   public void setFirstName(String firstName) {
       this.firstName = firstName;
   }

   // Getter method for 'lastName' field
   public String getLastName() {
       return lastName;
   }

   // Setter method for 'lastName' field
   public void setLastName(String lastName) {
   	  this.lastName = lastName;
   }

   // Getter method for 'designation' field
   public String getDesignation() {
       return designation;
   }

   // Setter method for 'designation' field
   public void setDesignation(String designation) {
       this.designation = designation;
   }

   // Getter method for 'age' field
   public int getAge() {
       return age;
   }

   // Setter method for 'age' field
   public void setAge(int age) {
       this.age = age;
   }
}

While the traditional JavaBeans approach for creating getter and setter methods manually gets the job done, but it has several drawbacks and limitations that make it less desirable, especially in modern Java development environments all, its drawbacks are majorly covered in the Lombok. 

So, instead of this, we prefer to use the Lombok pattern. Here is how it can be implemented in Java :

Data Object with Lombok

package org.example.dataobjects;

//importing getter and setter
import lombok.Getter;
import lombok.Setter;

//getter and setter annotations
@Getter
@Setter
public class Profile {
   private String firstName;
   private String lastName;
   private String designation;
   private int age;
}

Here is how we are setting data and for both approaches, the process is the same :

package org.example.datafactory;

import org.example.dataobjects.Profile;

public class ProfileData {

public Profile getProfileData() {
       Profile profile =new Profile();

       // Setting data for 'firstName' field
       profile.setFirstName("Parth");

       // Setting data for 'lastName' field
       profile.setLastName("Kathrotiya");

       // Setting data for 'age' field
       profile.setAge(23);

       // Setting data for 'designation' field
       profile.setDesignation("QA Automation Engineer");

       return profile;
   }
}

Constructor features of Project Lombok in Java

Constructors without Lombok we have to manually define each constructor, which can be tedious and error-prone, especially for classes with many fields. Additionally, we need to handle various constructor configurations, which can increase the complexity of the code.

Lombok simplifies this process with @NoArgsConstructor, @AllArgsConstructor and @RequiredArgsConstructor annotations. 

Constructors without Lombok

package org.example.dataobjects;

public class Profile {

   private String firstName;
   private String lastName;
   private String designation;
   private int age;

   // No-argument constructor
   public Profile() {
   }

   // Constructor with all fields
   public Profile(String firstName, String lastName, String designation, int age) {
       this.firstName = firstName;
       this.lastName = lastName;
       this.designation = designation;
       this.age = age;
   }

   // Constructor without 'age' field
   public Profile(String firstName, String lastName, String designation) {
       this.firstName = firstName;
       this.lastName = lastName;
       this.designation = designation;
   }
}

Using Lombok annotations reduces the amount of boilerplate code that needs to be written manually. With Lombok, you simply annotate the class and fields, and the constructors are generated automatically based on the specified criteria. This leads to cleaner and more concise code.

Constructors with Lombok

package org.example.dataobjects;

import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;

@NoArgsConstructor
@AllArgsConstructor
@RequiredArgsConstructor
public class Profile {
   private String firstName;
   private String lastName;
   private String designation;
   @NonNull private int age;
}

Various Lombok features and properties

1. ToString Generation

  • In Java, toString() is a method defined in the java.lang.Object class, which serves the purpose of returning a string representation of an object. The toString() method is inherited by all classes in Java, and its default implementation in the Object class returns a string containing the class name followed by the “at” symbol (@) and the hexadecimal representation of the object’s hash code.
  • However, the default implementation of toString() provided by Object may not always be meaningful or useful for specific classes. Therefore, it is common practice for developers to override the toString() method in their own classes to provide a custom string representation that better describes the state or properties of the object.
  • As per our example, a Profile class might override toString() to return a string containing the firstName, lastName, designation, age information. Overriding toString() allows to easily print or log object information in a human-readable format, which can be helpful for debugging, logging, or displaying information to users.
  • Without using ToString Lombok annotations we’ve to manually implement the toString() method within the Profile class. We concatenate the firstName, lastName, designation, and age fields to create the desired string representation. This manual implementation achieves the same result as Lombok’s @ToString annotation.

Without using ToString Annotations feature

package org.example.dataobjects;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class Profile {
   private String firstName;
   private String lastName;
   private String designation;
   private int age;

   @Override
   public String toString() {
       return "Profile{" +
               "firstName='" + firstName + '\'' +
               ", lastName='" + lastName + '\'' +
               ", designation='" + designation + '\'' +
               ", age=" + age +
               '}';
   }
}
  • The @ToString annotation generates a toString() method for the class, providing a string representation of its fields. No need to write one ourselves and maintain it as we enrich our data model.
  • With this annotation, calling toString() on an instance of profile will return a string containing the values of its fields.
  • @Exclude annotations can be useful for every various different annotations like Getters, Setters, ToString, EqualAndHashCode, etc. Let us understand that along with @ToString annotation example.
  • By annotating the designation field with @ToString(exclude = {“designation”}) 
  • Lombok excludes it from being included in the toString() method generated by @ToString. This can be useful if you want to avoid displaying certain fields in the string representation of an object.

Using ToString Annotations feature

package org.example.dataobjects;

import lombok.ToString;

@ToString(exclude = {"designation"})
public class Profile {

   private String firstName;
   private String lastName;
   private String designation;
   private int age;
}

2. EqualAndHashCode Generation

  • In Java, equals() and hashCode() are two methods commonly used to implement object equality and hash code generation, respectively.
  • equals() Method : The equals() method is used to compare two objects for equality. By default, the equals() method provided by the Object class compares object references, meaning it returns true only if the two objects being compared are the same instance in memory. However, it is often necessary to override the equals() method in custom classes to define a meaningful notion of equality based on object attributes.
  • hashCode() Method : The hashCode() method is used to generate a hash code value for an object. A hash code is an integer value that represents the state of an object and is typically used in hash-based data structures like hash tables. The hashCode() method is important because it allows objects to be efficiently stored and retrieved in hash-based collections.
  • In our example, we’ve manually implemented and override the equals() method to compare the fields of two Profile objects for equality, and the hashCode() method to generate a hash code based on the fields. 
  • We use the Objects.equals() method from the java.util.Objects class to compare the fields for equality, and Objects.hash() method to generate the hash code.
  • Without using EqualAndHashCode Annotations feature
package org.example.dataobjects;

public class Profile {
   private String firstName;
   private String lastName;
   private String designation;
   private int age;

   // Manual equals() method
   @Override
   public boolean equals(Object o) {
       if (this == o) return true;
       if (o == null || getClass() != o.getClass()) return false;

       Profile profile = (Profile) o;

       if (age != profile.age)
           return false;
       if (firstName != null ? !firstName.equals(profile.firstName) : profile.firstName != null)
           return false;
       if (lastName != null ? !lastName.equals(profile.lastName) : profile.lastName != null)
           return false;
       return designation != null ? designation.equals(profile.designation) : profile.designation == null;
   }

   // Manual hashCode() method
   @Override
   public int hashCode() {
       int result = firstName != null ? firstName.hashCode() : 0;
       result = 31 * result + (lastName != null ? lastName.hashCode() : 0);
       result = 31 * result + (designation != null ? designation.hashCode() : 0);
       result = 31 * result + age;
       return result;
   }
}
  • The @EqualsAndHashCode annotation generates equals() and hashCode() methods based on the class’s fields.
  • With this annotation, Lombok generates equals() and hashCode() methods using all fields of the class.
  • This eliminates the need for manual implementation of these methods, reducing boilerplate code and improving code maintainability.
  • Using EqualAndHashCode Annotations feature
package org.example.dataobjects;

import lombok.EqualsAndHashCode;

@EqualsAndHashCode
public class Profile {
   private String firstName;
   private String lastName;
   private String designation;
   private int age;
}

3. Data Annotations

  • Without using @Data annotations, we manually have to implement the getters, setters and Constructors features into our code.
  • Without using Data Annotations feature
package org.example.dataobjects;

import lombok.*;

//Adding individual annotations for all the features
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@RequiredArgsConstructor
public class Profile {
   private String firstName;
   private String lastName;
   private String designation;
   @NonNull private int age;
}
  • The @Data annotation is a convenient shortcut that bundles @Getter, @Setter, @NoArgsConstructor, @AllArgsConstructor, @RequiredArgsConstructor, @ToString, @EqualsAndHashCode and many more annotations.
  •  Using @Data, Lombok automatically generates these methods for us based on the fields declared in the class. This significantly reduces the amount of boilerplate code that we need to write and maintain, making our code more concise and readable.
  • Using Data Annotation feature
package org.example.dataobjects;

import lombok.*;

//No need to add individual annotation features instead of that add Data annotations which is library that contains all the features in it.
@Data
public class Profile {
   private String firstName;
   private String lastName;
   private String designation;
   @NonNull private int age;
}

4. BuilderPattern

  • Returning to our Profile example, constructing a new instance necessitates employing a constructor with potentially numerous count of four arguments, a task that becomes unwieldy as we introduce additional attributes to the class.
  • Thankfully, Lombok provides a robust solution with its @Builder feature, which facilitates the utilization of the Builder Pattern for creating new instances. Let’s integrate this feature into our Profile class.
package org.example.dataobjects;

import lombok.*;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@RequiredArgsConstructor
@ToString(exclude = {"designation"})
@EqualsAndHashCode
@Builder
@Data
public class Profile {
   private String firstName;
   private String lastName;
   private String designation;
   private int age;

public static void main(String[] args) {

   // Creating an instance of Profile using the builder
   Profile profile = Profile.builder()
           .firstName("Parth")
           .lastName("Kathrotiya")
           .designation("QA Automation Engineer")
           .age(23)
           .build();
   }
}

Delombok

  • Delombok is a tool provided by Project Lombok that reverses the effects of Lombok’s annotations, essentially “delombokifying” your code. It allows you to convert Java source code containing Lombok annotations into plain Java code by expanding the annotations and replacing them with the corresponding boilerplate code that they would generate.
  • The primary purpose of Delombok is to facilitate compatibility and interoperability with environments or tools that do not support Lombok annotations directly. For example, if you need to share your code with developers who do not have Lombok installed in their development environment, or if you want to analyse or refactor Lombok-annotated code using tools that do not understand Lombok annotations, you can use Delombok to convert the code into a form that is understandable and usable in those contexts.
  • Delombok can be invoked via the command line or integrated into build tools such as Maven or Gradle. When you run Delombok on your source code, it processes the Java files, expands the Lombok annotations, and generates new Java files without any Lombok annotations. The resulting code is functionally equivalent to the original code but without any dependency on Lombok.
  • Overall, Delombok is a useful tool provided by Project Lombok that enhances the interoperability and maintainability of codebases using Lombok annotations, allowing developers to leverage the benefits of Lombok while still ensuring compatibility with a wide range of development environments and tools.

Conclusion

  • While this post highlights the features I’ve found most beneficial, Lombok offers a plethora of additional functionalities and customizations.
  • Lombok’s documentation is an invaluable resource, providing in-depth explanations and examples for each annotation. If you’re intrigued by this post, I urge you to delve deeper into Lombok’s documentation to uncover even more possibilities.
  • Moreover, the project site offers comprehensive guides on integrating Lombok across various programming environments. Whether you’re using Eclipse, NetBeans, IntelliJ, or others, rest assured that Lombok seamlessly integrates with your workflow. As someone who frequently switches between IDEs, I can attest to Lombok’s versatility and reliability across all platforms.
  • Overall, Project Lombok offers a comprehensive set of features that streamline Java development, reduce code verbosity, and promote best practices.
  • Project Lombok offers a comprehensive set of features that streamline Java testing, reduce code verbosity, and promote best practices. By incorporating Lombok builders and Lombok constructors, testers can further simplify their code and improve maintainability.

Witness how our meticulous approach and cutting-edge solutions elevated quality and performance to new heights. Begin your journey into the world of software testing excellence. To know more refer to Tools & Technologies & QA Services.

If you would like to learn more about the awesome services we provide, be sure to reach out.

Happy Testing 🙂