Pendahuluan

Lombok adalah library java yang biasa digunakan untuk memudahkan kita dalam membuat class POJO (Plain Old Java Object) beserta method-method terkait POJO.

Pendahuluan mengenai Lombok, dapat dilihat di Lombok


Salah satu kebutuhan untuk POJO (Plain Old Java Object) adalah terkait Builder pattern.

Yaitu bagaimana membuat sebuah objek yang immutable, dengan cara yang mudah dan intuitif.

Membuat sebuah class dengan konsep Builder Pattern terkait dengan konsep :

  • immutability dari object.
  • readibility dari code kita.
  • kemudahan dalam konstruksi object baru.

Detail tentang Builder pattern (versi Joshua Bloch) dapat dilihat di Builder Pattern

Kalau kita lihat di artikel di link url diatas, maka untuk membuat sebuah class dengan Builder, maka diperlukan tambahan code khusus untuk class Builder nya sendiri.

Sehingga ukuran file dan line of code dari class awal kita bisa menjadi 2 kali lipat dengan adanya tambahan class Builder ini.

Dan sekali lagi, pembuatan class Builder ini juga merupakan hal yang repetitif untuk setiap class yang kita butuhkan.

Lombok menyederhanakan constructor dan code yang berhubungan dengan Builder pattern ini dengan 2 buah anotasi :

  • @Builder
  • @SuperBuilder


@Builder

Dengan anotasi @Builder, maka kita akan bisa membuat sebuah class dengan cara sbb :

1Person.builder()
2	.name("Adam Savage")
3	.city("San Francisco")
4	.job("Mythbusters")
5	.job("Unchained Reaction")
6	.build();

Ok, kita coba lihat untuk contoh lain.

Misalkan kita membuat sebuah classA dengan class buildernya di dalamnya juga tanpa menggunakan library Lombok, sbb :

 1package com.example.demo;
 2
 3public class ClassA
 4{
 5    private String attribute1;
 6    private int attribute2;
 7    private Integer attribute3;
 8
 9    ClassA(final String attribute1, final int attribute2, final Integer attribute3) {
10        this.attribute1 = attribute1;
11        this.attribute2 = attribute2;
12        this.attribute3 = attribute3;
13    }
14
15    public static ClassABuilder builder() {
16        return new ClassABuilder();
17    }
18
19    public String getAttribute1() {
20        return this.attribute1;
21    }
22
23    public int getAttribute2() {
24        return this.attribute2;
25    }
26
27    public Integer getAttribute3() {
28        return this.attribute3;
29    }
30
31	@Override
32	public int hashCode() {
33		return Objects.hash(attribute1, attribute2, attribute3);
34	}
35
36	@Override
37	public boolean equals(Object obj) {
38		if (this == obj) {
39			return true;
40		}
41		if (!(obj instanceof ClassA)) {
42			return false;
43		}
44		ClassA other = (ClassA) obj;
45		return Objects.equals(attribute1, other.attribute1) && attribute2 == other.attribute2
46				&& Objects.equals(attribute3, other.attribute3);
47	}
48
49	@Override
50	public String toString() {
51		return "ClassA [attribute1=" + attribute1 + ", attribute2=" + attribute2 + ", attribute3=" + attribute3 + "]";
52	}
53
54    // class Builder
55    public static class ClassABuilder
56    {
57        private String attribute1;
58        private int attribute2;
59        private Integer attribute3;
60
61        ClassABuilder() {
62        }
63
64        public ClassABuilder attribute1(final String attribute1) {
65            this.attribute1 = attribute1;
66            return this;
67        }
68
69        public ClassABuilder attribute2(final int attribute2) {
70            this.attribute2 = attribute2;
71            return this;
72        }
73
74        public ClassABuilder attribute3(final Integer attribute3) {
75            this.attribute3 = attribute3;
76            return this;
77        }
78
79        public ClassA build() {
80            return new ClassA(this.attribute1, this.attribute2, this.attribute3);
81        }
82
83        @Override
84        public String toString() {
85            return "ClassA.ClassABuilder [attribute1=" + this.attribute1 + ", attribute2=" + this.attribute2 + ", attribute3=" + this.attribute3 + "]";
86        }
87    }
88}

Seperti biasa, kalau kita lihat di classA diatas, terlalu banyak boilerplate code yang harus kita generate.

Termasuk didalamnya code untuk Builder.

Kalau kita lihat di classA diatas, maka tidak ada Setter attribute, sesuai dengan tujuan Builder pattern, yaitu agar instansiasinya dibuat dengan cara fluent interface dibandingkan dengan inisiasi lain.

Library Lombok berusaha untuk meminimalisir code yang berulang ini, termasuk pembuatan class Builder diatas, dengan bantuan anotasi.

Misalnya untuk memodifikasi classA diatas agar mempunyai class Builder dan fasilitas Lombok, kita tambahkan anotasi Getter, ToString,EqualsAndHashCode,Builder dari Lombok.

 1@Getter
 2@ToString
 3@EqualsAndHashCode
 4@Builder
 5public class ClassA {
 6
 7	private String attribute1;
 8	private int attribute2;
 9	private Integer attribute3;
10}

Pada akhirnya, kedua snippet code classAdi atas adalah sama isinya setelah dicompile lagi.

Kita bisa lihat banyaknya line of code yang bisa kita hemat menggunakan library Lombok sekaligus untuk membuat class Builder nya.

Oh, tentu saja, kita harus menambahkan library Lombok di pom.xml atau di dependency file tipe yang lain.

Agar anotasi @Data dan anotasi lain dari Lombok bisa dikenali.

Misalnya untuk maven, sbb :

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


@SuperBuilder

Hmm, kenapa ada anotasi @SuperBuilder ?

Untuk apa kah ?

@SuperBuilder digunakan untuk kasus Inheritance

misalkan :

 1public class ClassA {
 2	private String attribute1;
 3	private int attribute2;
 4	private Integer attribute3;
 5}
 6
 7public class ClassB extends ClassA {
 8	private String attributeB1;
 9	private int attributeB2;
10	private Integer attributeB3;
11}

Kita lihat ClassB merupakan child class dari ClassA

Ketika kita membutuhkan class Builder juga untuk kedua class, maka secara intuitif kita akan menambahkan sbb :

 1import lombok.Builder;
 2
 3@Builder
 4public class ClassA {
 5	private String attribute1;
 6	private int attribute2;
 7	private Integer attribute3;
 8}
 9
10import lombok.Builder;
11
12@Builder
13public class ClassB extends ClassA {
14	private String attributeB1;
15	private int attributeB2;
16	private Integer attributeB3;
17}

Tetapi kita akan menemukan error sbb :

  • The return type is incompatible with ClassA.builder()
  • Implicit super constructor ClassA() is undefined. Must explicitly invoke another constructor

The return type is incompatible with ClassA.builder()

Hal ini berarti sebenarnya @Builder ini ditujukan hanya untuk satu class saja, sementara kalau sudah menggunakan inheritance, maka akan ada 2 Builder yang ada didalamnya.

Efeknya compiler akan bingung dengan code yang digenerate oleh Lombok ini.



Implicit super constructor ClassA() is undefined. Must explicitly invoke another constructor

Kasus ini terkait dengan inisiasi konstruktor ClassB, yang didalamnya seharusnya ada pemanggilan konstruktor ClassA juga.

Ini merupakan cara konstruksi class di Java.

Dengan adanya Builder ini, maka constructor untuk classB sendiri dibuat otomatis oleh Lombok, tanpa melakukan pemanggilan constructor classA sebagai parentnya.

Oleh karenanya, kita akan mendapatkan error seperti diatas.

Hal ini karena @Builder ini secara natural hanya untuk satu class saja.

Oleh karena itu Lombok membuat sebuah anotasi lagi yaitu @SuperBuilder, yang mengakomodasi hal tersebut.

Contoh implementasinya sbb :

 1import lombok.experimental.SuperBuilder;
 2
 3@SuperBuilder
 4public class ClassA {
 5	private String attribute1;
 6	private int attribute2;
 7	private Integer attribute3;
 8}
 9
10import lombok.experimental.SuperBuilder;
11
12@SuperBuilder
13public class ClassB extends ClassA {
14	private String attributeB1;
15	private int attributeB2;
16	private Integer attributeB3;
17}

Mudah bukaan.

sehingga di class yang menggunakan classA dan classB kita bisa menginisiasi nya dengan cara sbb :

 1ClassA classA = ClassA.builder()
 2                 .attribute1("a")
 3				 .attribute2(14)
 4                 .attribute3(22)
 5				 .build();
 6
 7ClassB classB = ClassB.builder()
 8                  .attribute1("a")
 9				  .attribute2(14)
10				  .attribute3(22)
11				  .attributeB1("b")
12				  .attributeB2(8)
13                  .attributeB3(4)
14				  .build();