Witaj w trzeciej części serii Umiejętność obsługi IDE. Czytając dzisiejszy artykuł nauczysz się, jak w prosty i elegancki sposób możesz pozbyć się boilerplate code. Dzięki temu twój kod będzie krótszy, czytelniejszy, a zarazem bardziej elegancki.

Boilerplate code – co to jest i dlaczego czasami jest zły?

Czym jest boilerplate code, z którym już za chwilę będziemy bohatersko walczyli? Wspominałem już o nim w poprzedniej części. Jest to kod, który w sumie jest niezbędny, ale jednocześnie nie tworzy logiki programu. Pojawia się w wielu miejscach programu, występując w identycznej lub bardzo podobnej formule. Nie decyduje natomiast o tym, w jaki sposób program zaspokaja potrzeby użytkownika. W tym miejscu znajduje się ciekawy artykuł na ten temat.

Kojarzysz gettery i settery?


class Person {
     private String name;

     public String getName() { return name;}
     public void setName(String name) { this->name = name;}
}

Tego typu kod nie realizuje żadnej funkcji biznesowej. Nie wykonuje przecież żadnych operacji na bazie danych ani nic nie oblicza. Niemniej, po prostu musi występować. Dlaczego? W tym wypadku dobre praktyki programistyczne nakazują odwoływanie się do prywatnych atrybutów klasy za pomocą specjalnie stworzonych do tego celu metod. W przypadku gdy nasza klasa będzie miała kilkanaście atrybutów, np.: imię, nazwisko, wiek, data urodzenia, gettery i settery wygenerują kilkadziesiąt linijek kodu, które są praktycznie identyczne, ale muszą występować. To nie jedyny przykład boilerplate’u, który możemy napotkać w naszych programach.

Wiesz co robi metoda equals w Javie, prawda? Jeśli tak, domyślasz się pewnie, że jest ona często niejawnie wywoływana podczas korzystania z różnych mechanizmów języka Java. Popularnym przykładem jest np.: ArrayLista, którą chcielibyśmy przeszukać aby znaleźć konkretną osobę. W ten sposób Java wymusza na nas przesłonięcie metody equals, gdyż jest ona wykorzystywana do porównywania obiektów. No dobra, to zaimplementujmy tę metodę. Potem przychodzi pora na dodanie kolejnej klasy, np.: Car (samochód). Ona też będzie potrzebowała metody equals, która będzie wyglądała praktycznie identycznie jak ta z klasy Person!. Najprawdopodobniej nawet nie będziemy trudzili się ręcznie tylko zatrudnimy Intellij IDEA do wygenerowania tego rodzaju niezbędnego kodu. Pokazywałem jak to zrobić w poprzedniej części.

Do jakiego wniosku dochodzimy?

Boilerplate’a nikt nie chce, ale każdy go potrzebuje

Boilerplate jest niezbędny. Niesie on ze sobą kilka zalet, takich jak:

  • szybki development – typowy prosty javowy boilerplate potrafi wygenerować Intellij IDEA.

Ale jednocześnie jego obecność może potencjalnie prowadzić do dużej ilości problemów. Jakich, zapytasz?

  • bardzo duży wzrost liczby linijek programu – może to nie być wada sama w sobie. Możesz się przecież pochwalić, że twój program ma 10 tys. linii. Tylko co z tego, skoro 8 tys z nich jest tam, bo po prostu musi tak być?
  • zmniejszona czytelność kodu – wynika poniekąd z poprzedniego punktu. Więcej linii=więcej czasu musimy poświęcić na analizę=kod jest mniej czytelny
  • większa możliwość popełnienia błędu – statystycznie, czym więcej linii ma program tym więcej posiada błędów. W boilerplacie zwykle nie dzieje się nic, co mogłoby zakłócić działanie aplikacji. Ale wystarczy wprowadzenie jednej, pozornie nic nieznaczącej modyfikacji aby w przyszłości nasza aplikacja wysypała się w najmniej odpowiednim momencie.

Lombok – biblioteka języka Java, która rozwiązuje wszystkie problemu z boilerplatem!

Pewni dobrzy ludzie stworzyli taką bibliotekę do Javy, która zwie się Lombok i niesłychanie ułatwia życie. Nauczmy się z niej korzystać!

Na czym polega fenomen biblioteki Lombok? Wszystkie gettery, settery, a także metody które bardzo często nadpisujemy w identyczny sposób, takie jak equals, hashcode możemy zastąpić bardzo krótkimi adnotacjami! W najbardziej optymalnej wersji, wewnątrz niektórych klas (w szczególności takich, których jedynym zadaniem jest przechowywanie danych) zostaną wtedy tylko prywatne atrybuty. Żadnych konstruktorów i żadnych metod! Wszystko mogą zastąpić adnotacje. Prawda, że wygląda to arcyciekawie? No to zaczynajmy!

Instalacja biblioteki Lombok

Lombok najłatwiej zainstalować w projektach, które korzystają z Maven. Maven to system zarządzania zależnościami do projektu. Zajmuje się on automatycznym ściągnięciem wszystkich zależności oraz ich konfiguracją. Ty wtedy skupiasz się tylko i wyłącznie na programowaniu. Nie myślisz o tym, jak skonfigurować np.: classpath w taki sposób, aby kompilator Javy to widział.

UWAGA: W tej chwili zakładam, że posiadasz podstawowy projekt Maven wygenerowany za pomocą Intellij IDEA.

Dodajmy zależność Lombok. Wejdź w plik pom.xml. Odnajdź sekcję dependencies. (Jeśli nie istnieje – stwórz ją!). Dodaj wpis dependency w taki sposób, jak na listingu poniżej.


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

W prawym dolnym rogu Intellij IDEA pojawi się monit z pytaniem, czy chcesz zaimportować zmiany. Kliknij Import Changes. Możesz kliknąć też Enable Auto Import. Dzięki aktywacji tej opcji Intellij IDEA będzie importował zależności i konfigurował ponownie projekt za każdym razem, jak wprowadzisz i zapiszesz zmianę w pliku pom.xml.

Instalacja rozszerzenia dla Intellij IDEA

Ok. Lombok już jest zainstalowany i możemy bezproblemowo kompilować programy korzystające z jego dobrodziejstw. Jednakże nam, jako programistom w tej chwili będzie się bardzo ciężko programowało. Dlaczego? Ponieważ Intellij IDEA nie będzie widziało metod generowanych przez Lombok! Dlaczego? Po prostu musimy zainstalować plugin. Jak to zrobić?

  • Mając odpalony jakiś projekt w Intellij IDEA, na górnej belce okna klikamy File->Settings. W oknie, które nam się pojawi szukamy po lewej stronie opcji Plugins. Wybieramy nią.
  • W pole wyszukiwania u góry wpisujemy słowo Lombok. Wyświetli nam się lista pluginów. Lombok będzie oczywiście na samym początku 🙂
  • Klikamy zielony przycisk Install. (Na powyższym screenie Lombok jest już zainstalowany, dlatego jest pod nim szary przycisk Installed)

Potem restartujemy Intellij IDEA. Od tego momentu możemy cieszyć się pełną mocą Lomboka.

Person – mało logiki dużo boilerplate’u

Kod do optymalizacji
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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
package pl.kompikownia.lombokexample;

import java.math.BigDecimal;
import java.util.Date;
import java.util.Objects;

public class Person {
    private String name;
    private String surname;
    private Date date;
    private String PESEL;
    private BigDecimal salary;
    private int age;

    public Person() {
    }

    public Person(String name, String surname, Date date, String PESEL, BigDecimal salary, int age) {
        this.name = name;
        this.surname = surname;
        this.date = date;
        this.PESEL = PESEL;
        this.salary = salary;
        this.age = age;
    }

    public Person(String name, String surname, String PESEL) {
        this.name = name;
        this.surname = surname;
        this.PESEL = PESEL;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSurname() {
        return surname;
    }

    public void setSurname(String surname) {
        this.surname = surname;
    }

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }

    public String getPESEL() {
        return PESEL;
    }

    public void setPESEL(String PESEL) {
        this.PESEL = PESEL;
    }

    public BigDecimal getSalary() {
        return salary;
    }

    public void setSalary(BigDecimal salary) {
        this.salary = salary;
    }

    public int getAge() {
        return age;
    }

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

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age &amp;&amp;
                Objects.equals(name, person.name) &amp;&amp;
                Objects.equals(surname, person.surname) &amp;&amp;
                Objects.equals(date, person.date) &amp;&amp;
                Objects.equals(PESEL, person.PESEL) &amp;&amp;
                Objects.equals(salary, person.salary);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, surname, date, PESEL, salary, age);
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", surname='" + surname + '\'' +
                ", date=" + date +
                ", PESEL='" + PESEL + '\'' +
                ", salary=" + salary +
                ", age=" + age +
                '}';
    }
}

Kod, który będziemy optymalizowali jest taaaaki długi, że musiałem umieścić go w postaci rozwijanego bloczka tekstu 🙂 Jeśli go chcesz zobaczyć, rozwiń proszę kod za pomocą kliknięcia w powyższy link Kod do optymalizacji.

Lombok – optymalizacja klasy Person

Klasa Person, którą się zajmiemy, ma 110 linijek kodu. Dużo zajmują konstruktory, metody toString, equals, hashCode. Ale zdecydowanie najwięcej miejsca pochłaniają gettery i settery. To nimi zajmiemy się na samym początku.

Lombok – optymalizacja getterów – adnotacja @Getter

Adnotacja @Getter umieszczona nad klasą automatycznie generuje gettery dla wszystkich atrybutów klasy. Przykład jej użycia znajduje się poniżej:

Kod po I optymalizacji - adnotacja @Getter
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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
package pl.kompikownia.lombokexample;

import lombok.Getter;

import java.math.BigDecimal;
import java.util.Date;
import java.util.Objects;

@Getter
public class Person {
    private String name;
    private String surname;
    private Date date;
    private String PESEL;
    private BigDecimal salary;
    private int age;

    public Person() {
    }

    public Person(String name, String surname, Date date, String PESEL, BigDecimal salary, int age) {
        this.name = name;
        this.surname = surname;
        this.date = date;
        this.PESEL = PESEL;
        this.salary = salary;
        this.age = age;
    }

    public Person(String name, String surname, String PESEL) {
        this.name = name;
        this.surname = surname;
        this.PESEL = PESEL;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setSurname(String surname) {
        this.surname = surname;
    }

    public void setDate(Date date) {
        this.date = date;
    }

    public void setPESEL(String PESEL) {
        this.PESEL = PESEL;
    }

    public void setSalary(BigDecimal salary) {
        this.salary = salary;
    }

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

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age &amp;&amp;
                Objects.equals(name, person.name) &amp;&amp;
                Objects.equals(surname, person.surname) &amp;&amp;
                Objects.equals(date, person.date) &amp;&amp;
                Objects.equals(PESEL, person.PESEL) &amp;&amp;
                Objects.equals(salary, person.salary);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, surname, date, PESEL, salary, age);
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", surname='" + surname + '\'' +
                ", date=" + date +
                ", PESEL='" + PESEL + '\'' +
                ", salary=" + salary +
                ", age=" + age +
                '}';
    }
}

Co zyskaliśmy? Ilość linijek spadła ze 110 do 89. Jest to spadek o 20%. Prawda, że taki kod jest już nieco czytelniejszy? Za chwilkę w podobny sposób zrobimy porządek z setterami 🙂

Lombok – optymalizacja setterów – adnotacja @Setter

Na logikę, skoro gettery można wygenerować za pomocą adnotacji @Getter, do do stworzenia setterów będzie służyła adnotacja @Setter. Poniżej znajduje się przykład:

Kod po II optymalizacji - adnotacja @Setter
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
package pl.kompikownia.lombokexample;

import lombok.Getter;
import lombok.Setter;

import java.math.BigDecimal;
import java.util.Date;
import java.util.Objects;

@Getter
@Setter
public class Person {
    private String name;
    private String surname;
    private Date date;
    private String PESEL;
    private BigDecimal salary;
    private int age;

    public Person() {
    }

    public Person(String name, String surname, Date date, String PESEL, BigDecimal salary, int age) {
        this.name = name;
        this.surname = surname;
        this.date = date;
        this.PESEL = PESEL;
        this.salary = salary;
        this.age = age;
    }

    public Person(String name, String surname, String PESEL) {
        this.name = name;
        this.surname = surname;
        this.PESEL = PESEL;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age &amp;&amp;
                Objects.equals(name, person.name) &amp;&amp;
                Objects.equals(surname, person.surname) &amp;&amp;
                Objects.equals(date, person.date) &amp;&amp;
                Objects.equals(PESEL, person.PESEL) &amp;&amp;
                Objects.equals(salary, person.salary);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, surname, date, PESEL, salary, age);
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", surname='" + surname + '\'' +
                ", date=" + date +
                ", PESEL='" + PESEL + '\'' +
                ", salary=" + salary +
                ", age=" + age +
                '}';
    }
}

Nasz kod ma już tylko 67. Linijki. To niemal 50% mniej niż kod na samym początku. A to jeszcze nie koniec optymalizacji!

Lombok – optymalizacja getterów i setterów – adnotacja @Data

Jeśli potrzebujemy zarówno getterów jak i setterów, zamiast dwóch adnotacji: @Getter i @Setter, możemy zastosować pojedynczą adnotację @Data. Nie będę wklejał w tym miejscu przykładu kodu z tą zamianą. Zauważysz ją w kolejnych listingach, w których będę pokazywał jak zoptymalizować konstruktory.

Lombok – optymalizacja konstruktorów – @NoArgsConstructor i @AllArgsConstructor

Adnotacja @NoArgsConstructor generuje konstruktor, który nie przyjmuje żadnych argumentów. Natomiast @AllArgsConstructor generuje konstruktor który jako parametry przyjmuje wszystkie atrybuty klasy. Przykład ich użycia znajdziesz poniżej:

Kod po IV optymalizacji - RequiredArgsConstructor
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
package pl.kompikownia.lombokexample;

import lombok.*;

import java.math.BigDecimal;
import java.util.Date;
import java.util.Objects;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person {
    private String name;
    private String surname;
    private Date date;
    private String PESEL;
    private BigDecimal salary;
    private int age;

    public Person(String name, String surname, String PESEL) {
        this.name = name;
        this.surname = surname;
        this.PESEL = PESEL;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age &amp;&amp;
                Objects.equals(name, person.name) &amp;&amp;
                Objects.equals(surname, person.surname) &amp;&amp;
                Objects.equals(date, person.date) &amp;&amp;
                Objects.equals(PESEL, person.PESEL) &amp;&amp;
                Objects.equals(salary, person.salary);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, surname, date, PESEL, salary, age);
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", surname='" + surname + '\'' +
                ", date=" + date +
                ", PESEL='" + PESEL + '\'' +
                ", salary=" + salary +
                ", age=" + age +
                '}';
    }
}

Lombok – @RequiredArgsConstructor – w sytuacji, gdy potrzebujemy konkretnego konstruktora

Możemy wygenerować konstruktor bezargumentowy i konstruktor, który przyjmuje jako argumenty wszystkie atrybuty klasy. A co zrobić w sytuacji, gdy chcemy aby w konstruktorze były inicjowane tylko niektóre pola? Np.: name, surname i PESEL? Z pomocą przychodzi nam @RequiredArgsConstructor. Pola które chcemy umieścić w takim konstruktorze oznaczamy adnotacją @NonNull. Przykład znajduje się poniżej:

Kod po IV optymalizacji - RequiredArgsConstructor
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
package pl.kompikownia.lombokexample;

import lombok.*;

import java.math.BigDecimal;
import java.util.Date;
import java.util.Objects;

@Data
@NoArgsConstructor
@AllArgsConstructor
@RequiredArgsConstructor
public class Person {
    @NonNull
    private String name;
    @NonNull
    private String surname;
    private Date date;
    @NonNull
    private String PESEL;
    private BigDecimal salary;
    private int age;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age &amp;&amp;
                Objects.equals(name, person.name) &amp;&amp;
                Objects.equals(surname, person.surname) &amp;&amp;
                Objects.equals(date, person.date) &amp;&amp;
                Objects.equals(PESEL, person.PESEL) &amp;&amp;
                Objects.equals(salary, person.salary);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, surname, date, PESEL, salary, age);
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", surname='" + surname + '\'' +
                ", date=" + date +
                ", PESEL='" + PESEL + '\'' +
                ", salary=" + salary +
                ", age=" + age +
                '}';
    }
}

Lombok – @Equals i @Hashcode

Co nam jeszcze tak bruździ? Metody equals i hashcode i toString. Dwie pierwsze metody możemy wygenerować za pomocą oddzielnych adnotacji @Equals i @Hashcode. Podobnie jak w przypadku getterów i setterów, możemy posłużyć się także pojedynczą adnotacją @EqualsAndHashcode. Metodę toString generujemy natomiast za pomocą adnotacji o tej samej nazwie jak nazwa metody, czyli @ToString

Kod po V optymalizacji - equals, hashcode i toString
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package pl.kompikownia.lombokexample;

import lombok.*;

import java.math.BigDecimal;
import java.util.Date;

@Data
@NoArgsConstructor
@AllArgsConstructor
@RequiredArgsConstructor
@EqualsAndHashCode
@ToString
public class Person {
    @NonNull
    private String name;
    @NonNull
    private String surname;
    private Date date;
    @NonNull
    private String PESEL;
    private BigDecimal salary;
    private int age;
}

Odnieśliśmy sukces! Nasz kod ma teraz tylko 24 linijki i mieści się na jednym ekranie. To sprawia, że wystarczy tylko jeden rzut oka i wiemy wszystko o klasie!

Podsumowując

Więcej o bibliotece Lombok możesz znaleźć w dokumentacji. Powyżej wskazałem tylko podstawowe adnotacje, które używane są najczęściej. Lombok natomiast posiada o wiele więcej możliwości. Niektóre adnotacje z powyżej wymienionych możesz także „konfigurować”. Możesz np.: dodać nad atrybutem adnotację @Setter(AccessLevel.PRIVATE), dzięki temu setter do tego konkretnego atrybutu nie będzie dostępny na zewnątrz klasy. Po szczegóły odsyłam do dokumentacji dostępnej na tej stronie.

Dzięki za lekturę 🙂 Jeśli masz jakieś pytania – śmiało, pisz komentarz. Zachęcam cię również do polubienia mojego fanpage’a na Facebooku i włączenia powiadomień klikając ten dzwoneczek po lewej stronie. Do zobaczenia 🙂

Jeden komentarz

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *