Lombok common notes

In the process of programming, you will find that Java code is sometimes lengthy. Lombok provides a series of annotations to generate template code in the background and delete it from your class, which helps to keep the code clean and easier to read and maintain. The following will introduce the common annotations of Lombok to show you how to use Lombok to produce clearer and more concise code.

1. @NonNull

null checking method parameters is usually not a bad idea, especially if the API formed by the method is used by other developers. Although these checks are simple, they can become lengthy, especially when you have multiple parameters. As shown below, additional code does not contribute to readability and may distract attention from the main purpose of the method.

public void nonNullDemo(Employee employee, Account account) {

    if (employee == null) {
        throw new IllegalArgumentException("Employee is marked @NonNull but is null");
    }

    if (account == null) {
        throw new IllegalArgumentException("Account is marked @NonNull but is null");
    }

    // do stuff
}

Ideally, you need a null check - one that doesn't interfere. This is where @ NonNull works. By marking the parameter with @ NonNull, Lombok generates a null check for the parameter for you. Your method suddenly becomes more concise, but you don't lose those null checks for security.

public void nonNullDemo(@NonNull Employee employee, @NonNull Account account) {

    // just do stuff

}

By default, Lombok will throw NullPointerException. If you like, you can configure Lombok to throw IllegalArgumentException. Personally, I prefer IllegalArgumentException because I think it is more suitable for parameter checking.

2. More concise data classes

Data classes are areas where Lombok really helps reduce template code. Before looking at this option, think about the types of templates we often need to deal with. Data classes usually include one or all of the following:

  • Constructor (with or without parameters)
  • getter method of private member variable
  • setter method of private non final member variable
  • toString method to help log
  • equals and hashCode (handle equality / set)

The above can be generated through the IDE, so the problem is not the time it takes to write them. The problem is that simple classes with a small number of member variables can quickly become very verbose. Let's see how Lombok can reduce confusion by dealing with each of the above.

3.1 @Getter and @ Setter

For example, the following Car class. When generating getter s and setter s, you will get nearly 30 lines of code to describe a class containing three member variables.

public class Car {

    private String make;
    private String model;
    private String bodyType;

    public String getMake() {
        return make;
    }

    public void setMake(String make) {
        this.make = make;
    }

    public String getModel() {
        return model;
    }

    public void setModel(String model) {
        this.model = model;
    }

    public String getBodyType() {
        return bodyType;
    }

    public void setBodyType(String bodyType) {
        this.bodyType = bodyType;
    }

}

Lombok can generate Getter and Setter templates for you. By using @ Getter and @ Setter annotations on each member variable, you can finally get an equivalent class, as shown below:

public class Car {

    @Getter
    @Setter
    private String make;

    @Getter
    @Setter
    private String model;

    @Getter
    @Setter
    private String bodyType;

}

Note that you can only use @ Setter on non final member variables. Using @ Setter on final member variables will lead to compilation errors.

If you need to generate getters and setters for each member variable, you can also use @ getters and @ setters at the class level, as shown below.

@Getter
@Setter
public class Car {

    private String make;

    private String model;

    private String bodyType;

}

3.2 @AllArgsConstructor

A data class usually contains a constructor that accepts parameters for each member variable. The constructor generated by IDE for Car is as follows:

public class Car {

    @Getter
    @Setter
    private String make;

    @Getter
    @Setter
    private String model;

    @Getter
    @Setter
    private String bodyType;

    public Car(String make, String model, String bodyType) {
        super();
        this.make = make;
        this.model = model;
        this.bodyType = bodyType;
    }

}

We can use the @ AllArgsConstructor annotation to achieve the same function. Using @ Getter, @ Setter, @ AllArgsConstructor can reduce templates and keep classes cleaner and more concise.

@AllArgsConstructor
public class Car {

    @Getter
    @Setter
    private String make;

    @Getter
    @Setter
    private String model;

    @Getter
    @Setter
    private String bodyType;

}

There are other options for generating constructors@ RequiredArgsConstructor will create a constructor with each final member variable parameter, @ NoArgsConstructor
A constructor without parameters will be created.

3.3 @ToString

Overriding the toString method on your data class is a good practice to help log. The toString method generated by IDE for Car class is as follows:

@AllArgsConstructor
public class Car {

    @Getter
    @Setter
    private String make;

    @Getter
    @Setter
    private String model;

    @Getter
    @Setter
    private String bodyType;

    @Override
    public String toString() {
        return "Car [make=" + make + ", model=" + model + ", bodyType=" + bodyType+"]";
    }

}

We can replace this with ToString annotation, as follows:

@ToString
@AllArgsConstructor
public class Car {
    @Getter
    @Setter
    private String make;
    
    @Getter
    @Setter
    private String model;
    
    @Getter
    @Setter
    private String bodyType;
}

By default, Lombok generates a toString method that contains all member variables. Some member variables can be excluded through the override behavior of the exclude attribute @ ToString(exclude={"someField"},"someOtherField"}).

3.4 @EqualsAndHashCode

If you are comparing your data class with any type of object, you need to override the equals and hashCode methods. The equality of objects is defined based on business rules. For example, in the Car class, if two objects have the same make, model and bodyType, I may think they are equal. If I use the IDE generated equals method to check make, model, and bodyType, it will look like this:

@Override
public boolean equals(Object obj) {
    if (this == obj)
        return true;
    if (obj == null)
        return false;
    if (getClass() != obj.getClass())
        return false;
    Car other = (Car) obj;
    if (bodyType == null) {
        if (other.bodyType != null)
            return false;
    } else if (!bodyType.equals(other.bodyType))
        return false;
    if (make == null) {
        if (other.make != null)
            return false;
    } else if (!make.equals(other.make))
        return false;
    if (model == null) {
        if (other.model != null)
            return false;
    } else if (!model.equals(other.model))
        return false;
    return true;
}

The equivalent hashCode implementation is as follows:

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((bodyType == null) ? 0 : bodyType.hashCode());
    result = prime * result + ((make == null) ? 0 : make.hashCode());
    result = prime * result + ((model == null) ? 0 : model.hashCode());
    return result;
}

Although the IDE handles heavy work, we still have a lot of template code in our classes. Lombok allows us to use the @ EqualsAndHashCode class annotation to achieve the same function, as shown below:

@ToString
@AllArgsConstructor
@EqualsAndHashCode(exclude = {"yearOfManufacture", "cubicCapacity"})
public class Car {
    @Getter
    @Setter
    private String make;
    
    @Getter
    @Setter
    private String model;
    
    @Getter
    @Setter
    private String bodyType;
    
}

By default, @ EqualsAndHashCode creates equals and hashCode methods that contain all member variables. The exclude option can be used to tell Lombok to exclude certain member variables. In the code snippet above. I have excluded yearOfManuFacture and cubicCapacity from the generated equals and hashCode methods.

3.5 @Data

If you want to make data classes as concise as possible, you can use the @ data annotation@ Data is a shortcut to @ Getter, @ Setter, @ ToString, @ EqualsAndHashCode, and @ RequiredArgsConstructor.

@ToString
@RequiredArgsConstructor
@EqualsAndHashCode(exclude = {"model", "bodyType"})
public class Car {
    @Getter
    @Setter
    private String make;
    
    @Getter
    @Setter
    private String model;
    
    @Getter
    @Setter
    private String bodyType;
}

By using @ Data, we can simplify the above classes as follows:

@Data
public class Car {
    private String make;
    private String model;
    private String bodyType;
    private int yearOfManufacture;
    private int cubicCapacity;
}

4. Create objects with @ Buidler

The builder design pattern describes a flexible way to create objects. Lombok can help you easily implement this mode. Look at an example of using a simple Car class. Suppose we want to be able to create various Car objects, but we want flexibility in the properties we set when we create them.

@AllArgsConstructor
public class Car {
    private String make;
    private String model;
    private String bodyType;
    private int yearOfManufacture;
    private int cubicCapacity;
    private List<LocalDate> serviceDate;
}

Suppose we want to create a Car, but we just want to set up make and model. Using the standard all parameter constructor on Car means that we only provide make and model and set other parameters to null.

Car2 car2 = new Car2("Ford", "Mustang", null, null, null, null);

This is feasible but not ideal. We have to pass null for parameters we are not interested in. We can avoid this problem by creating a constructor that accepts only make and model. This is a reasonable solution, but not flexible enough. If we have many different field permutations, how can we create a new Car? Finally, we get a bunch of different constructors, representing all possible ways we can instantiate Car.

A clean and flexible way to solve this problem is to use the Builder model. Lombok helps you implement the Builder mode through @ Builder annotations. When you annotate the Car class with @ Builder, Lombok will perform the following operations:

  • Add a private constructor to Car
  • Create a static CarBuilder class
  • Create a setter style method for each member of the Car in CarBuilder
  • Add a construction method to create a new instance of Car in CarBuilder

Each setter style method on the CarBuilder returns its own instance (CarBuilder). This allows you to make method chained calls and provides a smooth API for object creation. Let's see how it works.

Car muscleCar = Car.builder().make("Ford")
        .model("mustang")
        .bodyType("coupe")
        .build();

Creating cars using only make and model is now simpler than before. Simply call the generated builder method on the Car to get the CarBuilder instance, and then invoke any setter style method that we are interested in. Finally, call build to create a new instance of Car.

Another convenient annotation worth mentioning is @ Singular. By default, Lombok creates standard setter style methods for collections that use collection parameters. In the following example, we created a new Car and set the service date list.

@Builder
public class Car {
    private String make;
    private String model;
    private String bodyType;
    private int yearOfManufacture;
    private int cubicCapacity;
    @Singular
    private List<LocalDate> serviceDate;
}

Adding @ Singular to a collection member variable provides an additional method that allows you to add a single item to the collection.

Car muscleCar3 = Car.builder()
        .make("Ford")
        .model("mustang")
        .serviceDate(LocalDate.of(2016, 5, 4))
        .build();

Now we can add a single service date as follows:

Car muscleCar3 = Car.builder()
        .make("Ford")
        .model("mustang")
        .serviceDate(LocalDate.of(2016, 5, 4))
        .build();

This is a quick way to help keep your code concise when working with collections during object creation.

5. Log

Another great feature of Lombok is the logger. If there is no Lombok, to instantiate a standard SLF4J logger, you usually have the following contents:

public class SomeService {
    
    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExample.class);

    public void doStuff() {
        log.debug("doing stuff....");
    }
}

These loggers are heavy and add unnecessary clutter to every class that needs logging. Fortunately, Lombok provides an annotation to create a logger for you. All you have to do is add annotations to the class, so it's OK.

@Slf4j
public class SomeService {
    public void doStuff() {
        log.debug("doing stuff....");
    }
}

The @ SLF4J annotation is used here, but Lombok can generate loggers for almost all common Java logging frameworks. Refer to the documentation for more logger options.

One thing about Lombok is its non intrusiveness. If you decide to implement your own method when using @ Getter, @ Setter or @ ToString, your method will always take precedence over Lombok. It allows you to use Lombok most of the time, but you still have control when you need it.

Thank you for your patience. If you have any suggestions, please send a private letter or comment

Keywords: Java Spring Boot Back-end

Added by travelerBT on Sat, 26 Feb 2022 12:30:35 +0200