«Lombok» – A java library to code more cleanly

«Lombok» – A java library to code more cleanly

 

LOMBOK

by Hyun Woo Son – Software Architect

Lombok is a library that help us write less code using annotations simplifying the code presented, and make the code more readable.

USE IT

To use it on maven:

<dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.4</version>
            <scope>provided</scope>
 </dependency>

On gradle:

compileOnly 'org.projectlombok:lombok:1.18.4'

HOW IT WORKS

Lomboks works on the compile stage adding the necessary code based on the annotations you added on each class.

TYPES

There are two types of annotations to use it now

  • stable
  • experimental

Stable : This is verified lombok annotations that work 100% and are very safe to use.

Experimental This is non verified lombok annotations and are currently on development or testing use it at your own risk and test it throughly

MOST USED ANNOTATIONS

I will not go through all the annotations but the most used and why, the rest it is available on the webpage with examples on this url:

Lombok web page

val

Val is just a local final variable

public String example(){
val example = new ArrayList<String>();
example.add("Hello, World!");
...
}
//without lombok

public String example(){
final ArrayList<String> example = new ArrayList<String>();
    example.add("Hello, World!");
...
}

var

Var is the same as val but it is mutable so you can override the object


public String example(){
val example = new ArrayList<String>();
example.add("Hello, World!");
example = new ArrayList<String(); // it will not launch compiler error.
...
}

cleanup

Automatic clean up or close for a resource, it invokes the close method to do it
if the resource has another method add the name of the resource on

@Cleanup("dispose")
@Cleanup InputStream in = new FileInputStream(args[0]);
    @Cleanup OutputStream out = new FileOutputStream(args[1]);

// the same as

InputStream in = new FileInputStream(args[0]);
    try {
      OutputStream out = new FileOutputStream(args[1]);
      try {
        byte[] b = new byte[10000];
        while (true) {
          int r = in.read(b);
          if (r == -1) break;
          out.write(b, 0, r);
        }
      } finally {
        if (out != null) {
          out.close();
        }
      }
    } finally {
      if (in != null) {
        in.close();
      }
    }

getter and setter

Adds the getter and setter to a bean or class if the annotation is on the class
it will create for all private properties

@Getter
@Setter
public class Mine{
}

To add only for each property add the annotation :

@Getter private int age;

Also you can disable or add different access level to the getters and setters

@Getter(AccessLevel.PROTECTED) private int age;

To override the parent getter or setter and disable getter and setter

@Getter(AccessLevel.NONE)
@Setter(AccessLevel.NONE)
private int age;

@toString

Adds toString to the class using all the non static fields by default

@ToString
public class ....

To exclude some properties add exclude:

@ToString(exclude={"name"})
public class Person{
private String name;
}

Equals and Hashcode

Creates the equals and hashcode methods on all non static fields

@EqualsAndHashCode

Also the exclude option is available

@EqualsAndHashCode(exclude={"name","age"})

This will create two methods of equal and hashcode

 @Override public boolean equals(Object o) {
    if (o == this) return true;
    if (!(o instanceof EqualsAndHashCodeExample)) return false;
    EqualsAndHashCodeExample other = (EqualsAndHashCodeExample) o;
    if (!other.canEqual((Object)this)) return false;
    if (this.getName() == null ? other.getName() != null : !this.getName().equals(other.getName())) return false;
    if (Double.compare(this.score, other.score) != 0) return false;
    if (!Arrays.deepEquals(this.tags, other.tags)) return false;
    return true;
  }

  @Override public int hashCode() {
    final int PRIME = 59;
    int result = 1;
    final long temp1 = Double.doubleToLongBits(this.score);
    result = (result*PRIME) + (this.name == null ? 43 : this.name.hashCode());
    result = (result*PRIME) + (int)(temp1 ^ (temp1 >>> 32));
    result = (result*PRIME) + Arrays.deepHashCode(this.tags);
    return result;
  }

Constructor methods

This constructor methods have in common a property of staticName=»», this will create a method static with the name given that calls the constructor.

@RequiredArgsConstructor(staticName="of")

This will add posibility to create an object using a static method like:

MapEntry.of("foo", 5)

@NoArgsConstructor

This will create a no argument constructor, add it to the class

@NoArgsConstructor

@RequiredArgsConstructor

Create a constructor with only the fields with final modifier

@RequiredArgsConstructor

@AllArgsConstructor

Creates a constructor with all the fields of your class

@AllArgsConstructor

Each of this constructors goes above the class declaration

@AllArgsConstructor
public class randomClass

@Data

This is a shortcut for @ToString, @EqualsAndHashCode, @Getter , @Setter, @RequiredArgsConstructor works nicely for vo classes or just beans, but if you want to add this on a managed entity, the to string annotation gives problems specially with the relations.

So it could be used but has to exclude any relations the entity has to avoid any references being considerated by the entity manager and causing a little entity chaos.

@Data
@ToString(exclude={"phonePersonal"})
@EqualsAndHashCode(exclude={"phonePersonal"})
@Entity
public class Person ...
{
@Column(name="phone_personal")
private Phone phonePersonal;

}

@Builder

Builder is a simple way to create a builder api for a class so a class:

@Builder
@Data // here it can be combined with other annotations
public class DataBuilder {

    @Builder.Default private String height = "0";
    private String name;
    private String age;

    @Singular private List<Integer> phones;
}

We see @Builder.default that sets a default value on the builder chain.

The @Singular works for List, Map, Set primarly this adds two methods
one that add a collection another to add one item to the collection.

So the class will be generated like this:

public class DataBuilder {

    @Builder.Default private String height = "0";
    private String name;
    private String age;

    private List<Integer> phones;

    @java.beans.ConstructorProperties({"height", "name", "age", "phones"})
    DataBuilder(String height, String name, String age, List<Integer> phones) {
        this.height = height;
        this.name = name;
        this.age = age;
        this.phones = phones;
    }

    public static DataBuilderBuilder builder() {
        return new DataBuilderBuilder();
    }

    public static class DataBuilderBuilder {
        private String height;
        private String name;
        private String age;
        private ArrayList<Integer> phones;

        DataBuilderBuilder() {
        }

        public DataBuilderBuilder height(String height) {
            this.height = height;
            return this;
        }

        public DataBuilderBuilder name(String name) {
            this.name = name;
            return this;
        }

        public DataBuilderBuilder age(String age) {
            this.age = age;
            return this;
        }

        public DataBuilderBuilder phone(Integer phone) {
            if (this.phones == null) this.phones = new ArrayList<Integer>();
            this.phones.add(phone);
            return this;
        }

        public DataBuilderBuilder phones(Collection<? extends Integer> phones) {
            if (this.phones == null) this.phones = new ArrayList<Integer>();
            this.phones.addAll(phones);
            return this;
        }

        public DataBuilderBuilder clearPhones() {
            if (this.phones != null)
                this.phones.clear();

            return this;
        }

        public DataBuilder build() {
            List<Integer> phones;
            switch (this.phones == null ? 0 : this.phones.size()) {
                case 0:
                    phones = java.util.Collections.emptyList();
                    break;
                case 1:
                    phones = java.util.Collections.singletonList(this.phones.get(0));
                    break;
                default:
                    phones = java.util.Collections.unmodifiableList(new ArrayList<Integer>(this.phones));
            }

            return new DataBuilder(height, name, age, phones);
        }

        public String toString() {
            return "DataBuilder.DataBuilderBuilder(height=" + this.height + ", name=" + this.name + ", age=" + this.age + ", phones=" + this.phones + ")";
        }
    }

This enables us to create objects like this:

var dataBuilder = DataBuilder.builder().age("1").name("2").build();

This allows for a much more clean and explicit way of creating objects specially if you have an object with a lot of fields and it is needed to create without remembering positions like in a constructor.

var dataBuilder = DataBuilder.builder().age("2").name("3").phone(1).build();

This we are looking for how it adds just one phone to the list. This results on:

 data Builder DataBuilder(height=0, name=3, age=2, phones=[1])

Log

For logging instead of create on every class the logger that usually goes

private static final Logger log = LoggerFactory.getLogger(....

Just add the @Log, @Sl4fj , @CommonsLog to the class


@Log
public class LogExample {

  public static void main(String... args) {
    log.severe("Something's wrong here");
  }
}

@Slf4j
public class LogExampleOther {

  public static void main(String... args) {
    log.error("Something else is wrong here");
  }
}

@CommonsLog(topic="CounterLog")
public class LogExampleCategory {

  public static void main(String... args) {
    log.error("Calling the 'CounterLog' with a message");
  }
}

Its better to use @Sl4fj to log , for it provides a faced for logging, giving you the posibility to use whatever logging library underneath.

There are more of lombok features that are not discussed here, so go to:

https://projectlombok.org

Leave a Reply

Your email address will not be published.

Advance Latam
Cuéntenos su proyecto y permítanos ayudarle.