Core Java

How to write less and better code, or Project Lombok

I have long intended to write about Project Lombok, so much so that I am probably doing it when every self-respecting Java developer has already heard about it. Nevertheless, it is worth mentioning, if only to remind myself that one should not hesitate to try performance-enhancing tools and see if they fit, and Lombok is certainly enhancing performance of a Java-coder by allowing to simultaneously write less code and add to its quality, which is no small matter.

What is it that Java opponents usually say about its weaknesses?

Java is too verbose.
(c) Every Java opponent

Unfortunately, there’s a lot of truth in this statement. Imagine a simple data class you want to have to store the personal information – name, age etc. It might look like this.

  
 public class PersonSimple {
    private String lastName;
    private String firstName;
    private Integer age;

    public String getLastName() {
        return lastName;
    }

    public String getFirstName() {
        return firstName;
    }

    public Integer getAge() {
        return age;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

OK, you say. I generated all this stuff with the IDE, wasn’t that hard. But we also need a hashCode() and equals(). Because you might want to keep the instances in collections and check equality. No problem, most IDEs will allow you to generate these as well as getters and setters. And they will throw in a toString()  generator to help you output the objects and see what’s in them.

  
 public class PersonSimple {
    private String lastName;
    private String firstName;
    private Integer age;

    public String getLastName() {
        return lastName;
    }

    public String getFirstName() {
        return firstName;
    }

    public Integer getAge() {
        return age;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        PersonSimple that = (PersonSimple) o;
        return Objects.equals(lastName, that.lastName) &&
                Objects.equals(firstName, that.firstName) &&
                Objects.equals(age, that.age);
    }

    @Override
    public int hashCode() {
        return Objects.hash(lastName, firstName, age);
    }

    @Override
    public String toString() {
        return "PersonSimple{" +
                "lastName='" + lastName + '\'' +
                ", firstName='" + firstName + '\'' +
                ", age=" + age +
                '}';
    }
}

OK then. All this stuff was generated by IntelliJ IDEA. It is not that difficult right? Well no. But now you are thinking of Josh Bloch and decide to apply a Builder pattern. This time, you have to do a little manual work. What you will likely have in the end will be something close to this.

  
public class PersonSimple {
    private final String lastName;
    private final String firstName;
    private final Integer age;

    private PersonSimple(String lastName, String firstName, Integer age) {
        this.lastName = lastName;
        this.firstName = firstName;
        this.age = age;
    }

    public String getLastName() {
        return lastName;
    }

    public String getFirstName() {
        return firstName;
    }

    public Integer getAge() {
        return age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        PersonSimple that = (PersonSimple) o;
        return Objects.equals(lastName, that.lastName) &&
                Objects.equals(firstName, that.firstName) &&
                Objects.equals(age, that.age);
    }

    @Override
    public int hashCode() {
        return Objects.hash(lastName, firstName, age);
    }

    @Override
    public String toString() {
        return "PersonSimple{" +
                "lastName='" + lastName + '\'' +
                ", firstName='" + firstName + '\'' +
                ", age=" + age +
                '}';
    }
    
    public static class Builder {
        private String lastName;
        private String firstName;
        private Integer age;

        public Builder setLastName(String lastName) {
            this.lastName = lastName;
            return this;
        }

        public Builder setFirstName(String firstName) {
            this.firstName = firstName;
            return this;
        }

        public Builder setAge(Integer age) {
            this.age = age;
            return this;
        }
        
        public PersonSimple build() {
            return new PersonSimple(lastName, firstName, age);
        }
     }
}

So. We have a builder, and now our PersonSimple can be created with a piece of code like this.

  
 final PersonSimple john = new Person.Builder()
                .setFirstName("John")
                .setLastName("Doe")
                .setAge(30)
                .build();
        System.out.println(john);

But you had to create so many things. You have:

  • a data class with an all-arguments private constructor;
  • three getters on a data class;
  • an accompanying builder class with three setters;
  • a build() method on a builder class that calls the private data class constructor;
  • and don’t forget the hashCode(), equals() and toString() methods, though generated.

The code now takes more than 70 lines. And every time you need a new field, you will have to take care of it in at least three places – getter in the data class, setter in the builder class, and the constructor.

What if I were to show you how to do the same things with Project Lombok?

OK, here goes.

  
 @Builder(toBuilder = true)
@ToString
@EqualsAndHashCode
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class Person {
    @NonNull
    @Getter
    private final String lastName;
    @NonNull
    @Getter
    private final String firstName;
    @NonNull
    @Getter
    private final Integer age;
}
  • We generated the builder class with @Builder annotation. toBuilder=true means that we additionally created a toBuilder() instance method that creates a new instance of the builder class, initialized with the values from the current instance.
  • We added a toString() method with a @ToString annotation.
  • We added hashCode() and equals() with, you guessed it, @EqualsAndHashCode.
  • We made the all-argument constructor private with @AllArgsConstructor(access = AccessLevel.PRIVATE).
  • We added standard getters to the class fields with @Getter annotations.

It is now fifteen lines of code. Fifteen! We just decreased the code five times. The gain would be even better for a class with a lot of fields.

So, what exactly does Project Lombok do? It generates all the boilerplate during compile time, allowing you to avoid writing that code manually or generating it with an IDE. It saves you a lot of time and allows to create prettier code with less effort.

After the lombokization of your code, the person can be created like this.

  
  private static Person JOHN = Person.builder()
            .firstName("John")
            .lastName("Doe")
            .age(30)
            .build();

To add Lombok to your project, you need to add a dependency for it and also in my case install a Lombok plugin for IDEA. The Gradle configuration is described here and maven here.

All the features of Lombok are described here. Please have a look and see if there’s anything else that might come in useful, because what I described here is just a small part of what it has.

The code from examples is stored in my github repository.

I wish you clean and concise code!

Published on Java Code Geeks with permission by Maryna Cherniavska, partner at our JCG program. See the original article here: How to write less and better code, or Project Lombok

Opinions expressed by Java Code Geeks contributors are their own.

Maryna Cherniavska

Maryna has productively spent 10+ years in IT industry, designing, developing, building and deploying desktop and web applications, designing database structures and otherwise proving that females have a place among software developers. And this is a good place.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

5 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
phillip
phillip
6 years ago

Hmmm, I agree Lombok is a great tool, but I’ve always strongly disagreed using it in a large real life project… Here is why: * We’re already heavily relying on bytecode manipulation tools like aspectj with Spring and friends in the Java world. More bytecode manipulation tends to make the application behavior less deterministic when live in production. It’s also true in tests contexts where we use instrumentation like cobertura etc. * Lombok-enabled classes/jars can’t be used easily anymore by third parties. Ex: if you happen to debug step by step a code that uses lombok, one must use a… Read more »

AnonymousKotlinLover
AnonymousKotlinLover
6 years ago

Or just use kotlin ;)

phillip
phillip
6 years ago

Agreed. If productivity on the jvm is your first goal, use kotlin or groovy or whatever :)

Back to top button