Lombok: Simplify your development of Java code

Annotations Made Java Coding Simpler


29 Oct 2016 View Comments
#lombok #java #computer

Lombok Reference Guide

Lombok is one of my favorite Java library which is designed to save you quite a bit of coding on stuff that can get quite repetitive/boilerplate. Lombok is there to the rescue! This is more of a reference guide/cheat sheet for me to come and take a look on some of the usage. I always wanted a single page that has a good summarization of annotations I often use:

Project Lombok will reduce quite a bit of code by introducing simple annotations.

Let me describe each features of, getters/setters, override equals, hashCode, toString and/or offer a copy constructor.

By annotating @Getter or @Setter lets lombok generate default getter/setter automatically. It automattically creates with the name: getField, setField. Below are the comparisons:

Plain Vanilla Java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
 public class GetterSetterExample {
   /**
    * Age of the person. 
    */ 
   private int age = 10;
 
   /**
    * Name of the person.
    */ 
   private String name;
   
   @Override public String toString() {
     return String.format("%s (age: %d)", name, age);
   }  
   
   /**
    * Gets the age of the person.
    *  
    * @return The current value of this person's age.
    */ 
   public int getAge() {
     return age;
   }  
   
   /**
    * Sets the age of the person.
    *  
    * @param age New value for this person's age.
    */ 
   public void setAge(int age) {
     this.age = age;
   }  
   
   /**
    * Sets the name of this person.
    *  
    * @param name The new value.
    */ 
   protected void setName(String name) {
     this.name = name;
   }  
 }

With Lombok:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 import lombok.AccessLevel;
 import lombok.Getter;
 import lombok.Setter;
 
 public class GetterSetterExample {
   /**
    * Age of the person.
    *  
    * @param age New value for this person's age.
    * @return The current value of this person's age.
    */ 
   @Getter @Setter private int age = 10;
   
   /**
    * Name of the person.
    *
    * Sets the name of this person.
    *  
    * @param name The new value.
    */ 
   @Setter(AccessLevel.PROTECTED) private String name;
   
   @Override public String toString() {
     return String.format("%s (age: %d)", name, age);
   }  
 }

Result: 42 lines vs 26 lines

Using Lombok, you can annotate with a @ToString and Lombok will override our toString method and print out the classes fields.

Plain Vanilla Java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
 import java.util.Arrays;
 
 public class ToStringExample {
   private static final int STATIC_VAR = 10;
   private String name;
   private Shape shape = new Square(5, 10);
   private String[] tags;
   private int id;
   
   public String getName() {
     return this.getName();
   }  
   
   public static class Square extends Shape {
     private final int width, height;
        
     public Square(int width, int height) {
       this.width = width;
       this.height = height;
     }  
        
     @Override public String toString() {
       return "Square(super=" + super.toString() + ", width=" + this.width + ", height=" + this.height + ")";
     }  
   }  
   
   @Override public String toString() {
     return "ToStringExample(" + this.getName() + ", " + this.shape + ", " + Arrays.deepToString(this.tags) + ")";
   }  
 }

With Lombok:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 import lombok.ToString;
 
 @ToString(exclude="id")
 public class ToStringExample {
   private static final int STATIC_VAR = 10;
   private String name;
   private Shape shape = new Square(5, 10);
   private String[] tags;
   private int id;
   
   public String getName() {
     return this.getName();
   }
   
   @ToString(callSuper=true, includeFieldNames=true)
   public static class Square extends Shape {
     private final int width, height;
        
     public Square(int width, int height) {
       this.width = width;
       this.height = height;
     }
   }
 }

Result: 30 lines vs 24 lines

As you would already know, equals and HashCode come in as a package and they definitely should be implemented together. With this annotation, Lombok will generate both equals and hashCode methods at compile time. Please keep in mind this auto-generated codes will create the basic equals and hashcode. If you need to apply some customized behaviors to it, I would encourage you to write your own methods to handle it. Comparison

Plain Vanilla Java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import java.util.Arrays;

public class EqualsAndHashCodeExample {
  private transient int transientVar = 10;
  private String name;
  private double score;
  private Shape shape = new Square(5, 10);
  private String[] tags;
  private int id;
  
  public String getName() {
    return this.name;
  }
  
  @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;
  }
  
  protected boolean canEqual(Object other) {
    return other instanceof EqualsAndHashCodeExample;
  }
  
  public static class Square extends Shape {
    private final int width, height;
    
    public Square(int width, int height) {
      this.width = width;
      this.height = height;
    }
    
    @Override public boolean equals(Object o) {
      if (o == this) return true;
      if (!(o instanceof Square)) return false;
      Square other = (Square) o;
      if (!other.canEqual((Object)this)) return false;
      if (!super.equals(o)) return false;
      if (this.width != other.width) return false;
      if (this.height != other.height) return false;
      return true;
    }
    
    @Override public int hashCode() {
      final int PRIME = 59;
      int result = 1;
      result = (result*PRIME) + super.hashCode();
      result = (result*PRIME) + this.width;
      result = (result*PRIME) + this.height;
      return result;
    }
    
    protected boolean canEqual(Object other) {
      return other instanceof Square;
    }
  }
}

With Lombok:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import lombok.EqualsAndHashCode;

@EqualsAndHashCode(exclude={"id", "shape"})
public class EqualsAndHashCodeExample {
  private transient int transientVar = 10;
  private String name;
  private double score;
  private Shape shape = new Square(5, 10);
  private String[] tags;
  private int id;
  
  public String getName() {
    return this.name;
  }
  
  @EqualsAndHashCode(callSuper=true)
  public static class Square extends Shape {
    private final int width, height;
    
    public Square(int width, int height) {
      this.width = width;
      this.height = height;
    }
  }
}

Result: 72 lines vs 25 lines

@NoArgsConstructor will generate a constructor with no parameters. Just like how Object class would generate one for you. If not possible due to final variables, then compile error will happen. You can do something like @NoArgsConstructor(force = true) which will default the final values to default values (0 / false / null).

Plain Vanilla Java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class NoArgsExample {
    private String field;

    public NoArgsExample() {
    }

    public String getField() {
        return field;
    }

    public void setField(String field) {
        this.field = field;
    }

    public static void main(String[] args) {
        NoArgsExample example = new NoArgsExample();
        example.setField("test");
        System.out.println(example.field);
    }
}

With Lombok:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
public class NoArgsExample {
    private String field;

    public static void main(String[] args) {
        NoArgsExample example = new NoArgsExample();
        example.setField("test");
        System.out.println(example.field);
    }
}

Result: 20 lines vs 16 lines

@AllArgsConstructor basically means to use every variable defined to be a base constructor. It’d be easier if you just read examples below :p

Plain Vanilla Java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import lombok.NonNull;

public class ConstructorExample<T> {
    private int x, y;
    @NonNull
    private T description;

    private ConstructorExample(T description) {
        if (description == null)
            throw new NullPointerException("description");
        this.description = description;
    }

    public static <T> ConstructorExample<T> of(T description) {
        return new ConstructorExample<T>(description);
    }

    @java.beans.ConstructorProperties({ "x", "y", "description" })
    protected ConstructorExample(int x, int y, T description) {
        if (description == null)
            throw new NullPointerException("description");
        this.x = x;
        this.y = y;
        this.description = description;
    }

    public static class OneArgExample {
        private String field;

        public OneArgExample(String field) {
            this.field = field;
        }

        @Override
        public String toString() {
            return "OneArgExample{" +
                    "field='" + field + '\'' +
                    '}';
        }
    }

    public static void main(String[] args) {
        OneArgExample example = new OneArgExample("test");
        ConstructorExample typeExample = ConstructorExample.of(example);
        System.out.println(typeExample.description);

        ConstructorExample allArgsExample = new ConstructorExample(1,2,example);
        System.out.println(allArgsExample.x);
        System.out.println(allArgsExample.y);
        System.out.println(allArgsExample.description);
    }
}

With Lombok:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import lombok.AllArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.ToString;

@AllArgsConstructor
public class ConstructorExample<T> {
    private int x, y;
    @NonNull
    private T description;

    private ConstructorExample(T description) {
        if (description == null)
            throw new NullPointerException("description");
        this.description = description;
    }

    public static <T> ConstructorExample<T> of(T description) {
        return new ConstructorExample<T>(description);
    }

    @RequiredArgsConstructor
    @ToString
    public static class OneArgExample {
        @NonNull private String field;
    }

    public static void main(String[] args) {
        OneArgExample example = new OneArgExample("test");
        ConstructorExample typeExample = ConstructorExample.of(example);
        System.out.println(typeExample.description);

        ConstructorExample allArgsExample = new ConstructorExample(1,2,example);
        System.out.println(allArgsExample.x);
        System.out.println(allArgsExample.y);
        System.out.println(allArgsExample.description);
    }
}

Result: 52 lines vs 38 lines

@Data annotation is a convenient shortcut annotation that bundles the features of @ToString, @EqualsAndHashCode, @Getter / @Setter and @RequiredArgsConstructor together. I will use an example above to show you how it can further reduce:

Remember we had an example above in Lombok:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
public class NoArgsExample {
    private String field;

    public static void main(String[] args) {
        NoArgsExample example = new NoArgsExample();
        example.setField("test");
        System.out.println(example.field);
    }
}

Now if we used, @Data instead,

1
2
3
4
5
6
7
8
9
10
11
import lombok.Data;

@Data public class NoArgsExample {
    String field;

    public static void main(String[] args) {
        NoArgsExample example = new NoArgsExample();
        example.setField("test");
        System.out.println(example.getField());
    }
}

Result: 16 lines further reduced to 11 lines of code

As you can see, Lombok can reduce quite a bit of line of code by simply applying annotations. I personally think Lombok is a must for Java codes. You might feel it is hackish at first, but you will soon understand how Lombok simplifies your world of Java and modernizes Java.

Share this post

Me

I am a passionate software developer working in Downtown, Vancouver. I strongly believe in art of algorithms and together with it to write clean and efficient software to build awesome products. If you would like to connect with me, choose one from below options :) You can also send me an email at