Biblioteka lombok

Lombok

Jeśli dopiero rozpoczynasz swoją przygodę z programowaniem w języku Java to możliwe, że do tej pory jeszcze nie słyszałeś o narzędziu Lombok. W przypadku, gdy słyszałeś o tej cudownej bibliotece, to nic straconego – może dowiesz się o niej czegoś nowego. Ja niestety dopiero o tym narzędziu usłyszałem w swojej pierwszej pracy i żałuję, że nie znałem tego narzędzia wcześniej. Z pewnością ułatwiłoby mi tworzenie prywatnych projektów.

W tym wpisie przedstawię Ci możliwości Lomboka oraz pokażę jak może on przyśpieszyć Ci pisanie kodu. Oprócz tego, wspomnę też o problemach jakie Lombok potrafi nam dostarczyć.

Czym jest Lombok?

Lombok jest biblioteką, która jest w stanie generować kod tzw. boilerplate. Kod, który w tym przypadku i tak prawdopodobnie wygenerowałbyś w swoim IDE, ponieważ pisanie go ręcznie jest monotonne i nie ma w nim nic nadzwyczajnego. Lombok, więc umożliwia Ci wygenerowanie:

  • Konstruktorów,
  • Getterów,
  • Setterów,
  • Buildera (jeśli nie wiesz o czym mowa to warto poznać wzorzec projektowy budowiczny – o tutaj),
  • Metody equals i hashCode,
  • Metody toString.

Do tego oferuje takie przydatne mechanizmy jak:

  • przy stworzeniu obiektu sprawdzanie czy pole nie jest nullem,
  • synchronizowanie pola klasy (przydatne podczas programowania wielowątkowego),
  • SneakyThrowException – brak konieczności “przepychania” checked exception np. IOException,
  • inicjalizacja loggera jako pole klasy,
  • “sprzątanie” po utworzonych zasobach np. zamykanie otworzonych połączeń.

I to wszystko dostarcza Lombok korzystając jedynie z kilku, łatwych do zapamiętania adnotacji.

Konfiguracja lomboka

Konfiguracja biblioteki Lombok jest bardzo prosta, tak naprawdę wiąże się to z dodaniem zależności do projektu.

W przypadku projektu Maven, wystarczy do pliku pom.xml dodać poniższy fragment.

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

Jeśli korzystasz z narzędzia Gradle, dodaj poniższy fragment do pliku build.gradle.

dependencies {
    compileOnly 'org.projectlombok:lombok:1.18.4'
    annotationProcessor 'org.projectlombok:lombok:1.18.4'
}

Jedyną rzeczą, którą trzeba jeszcze zrobić to zainstalować odpowiednią wtyczkę do swojego IDE. Jest to spowodowane tym, że Lombok generuje kod w fazie annotation processingu. Oznacza to, że IDE domyślnie nie wie o istnieniu wygenerowanych metod zaraz przed etapem kompilacji kodu źródłowego. Potrzebny do tego jest właśnie wcześniej wspomiany plugin.

W przypadku, gdy używasz IntelliJ Idea, wystarczy wykonać dwa poniższe kroki:

  1. Przejść do widoku Settings | Build, Execution, Deployment | Compiler | Annotation Processors, w tym oknie musisz włączyć dwie opcje: Enable annotation processing oraz Obtain processors from project classpath
  2. Zainstalować plugin Lombok i uruchomić ponownie IntelliJ

Po ich wykonaniu, będziesz mógł już korzystać w swoim kodzie z wygenerowanych przez Lomboka metod.

Jeśli korzystasz z Eclipse skorzystaj z instrukcji z oficjalnej strony biblioteki Lombok.

Możliwości

Generowanie konstruktorów

Do wygenerowania konstruktora w klasie masz do dyspozycji trzy następujące adnotacje:

  • @NoArgsConstructor,
  • @AllArgsConstructor,
  • @RequiredArgsConstructor

Użycie ich nie jest skomplikowane, nazwy adekwatnie odpowiadają ich działaniom. Wystarczy wybrane adnotacje dodać nad klasą, w której chcemy wygenerować kod i to wszystko! Poniżej przedstawię Ci działanie każdego z nich w praktyce – na bardzo prostych przykładach.

@NoArgsConstructor

Adnotacja ta wydaje się najprostsza ze wszystkich wymienionych powyżej. Jest ona po prostu odpowiedzialna za wygenerowanie konstruktora bezargumentowego. Warto zwrócić uwagę, że jeśli mamy choć jedno pole final bez domyślnej wartości to użycie @NoArgsConstructor nie będzie możliwe (wynika to stricte z języka Java). Jeśli w takim przypadku koniecznie potrzebujesz konstruktora bezargumentowego to musisz go stworzyć sam.

import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;

import java.util.UUID;


enum Sex { MALE, FEMALE }

@NoArgsConstructor
class User {
    private final String uuid = UUID.randomUUID().toString();
    private String name;
    private String surname;
    private int age;
    private Sex sex = Sex.MALE;
}

public class Main {
    public static void main(String[] args) {
        User unknown = new User();
    }
}

@AllArgsConstructor

Następna na liście jest adnotacja @AllArgsConstructor, która tworzy konstruktor z wszystkimi polami klasy.

import lombok.AllArgsConstructor;

import java.util.UUID;

enum Sex { MALE, FEMALE }

@AllArgsConstructor
class User {
    private final String uuid = UUID.randomUUID().toString();
    private final String name;
    private final String surname;
    private int age;
    private Sex sex = Sex.MALE;
}

public class Main {
    public static void main(String[] args) {
        User user = new User("Pablo", "Escobar", 33, Sex.MALE);
    }
}

Jak można zauważyć na powyższym przykładzie, Lombok bierze pod uwagę wszystkie pola klasy jakich tylko wartość można zmienić podczas tworzenia obiektu. Zwróć uwagę, że pole uuid zostało pominięte, ponieważ jest oznaczone jako final, a wartość została przypisana bezpośrednio przy deklaracji pola.

@RequiredArgsConstructor

Ostatnią adnotacją jest @RequiredArgsConstructor  – generując konstruktor bierze pod uwagę jedynie pola finalne klasy, oczywiście pomijając te, które zostały już zainicjalizowane (tak jak w poprzednim przykładzie).

import lombok.RequiredArgsConstructor;


enum Sex { MALE, FEMALE }

@RequiredArgsConstructor
class User {
    private final String name;
    private final String surname;
    private int age;
    private Sex sex = Sex.MALE;
}

public class Main {
    public static void main(String[] args) {
        User user = new User("Pablo", "Escobar");
    }
}

Modyfikacja dostępu

Wszystkie powyższe adnotacje mają jedną istotną opcję, która powinna Cię zainteresować – access. Opcja ta umożliwia wybranie modyfikatora dostępu dla wygenerowanego konstruktora. Użycie jest banalne i wygląda tak:

@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
class User extends BaseEntity {
    private final String name;
    private final String surname;
}

Dzięki dodaniu tej opcji, stworzony konstruktor będzie widoczny jako protected.

@Getter i @Setter

Następnie poruszymy kwestię adnotacji @Getter i @Setter. Pewnie już się domyślasz ich działania, jednak żeby nie było niedomówień, to po krótce przedstawię ich działanie.

Adnotacja @Getter jak sama nazwa wskazuje jest odpowiedzialna za wygenerowanie getterów w klasie. W przypadku, gdy użyjemy jej nad klasą, to zostaną wygenerowane gettery dla wszystkich z pól.

import lombok.Getter;

@Getter
class User {
    private String name;
    private String surname;
}

public class Main {
    public static void main(String[] args) {
        User user = new User();
        System.out.println(user.getName());
        System.out.println(user.getSurname());
    }
}

Można zadeklarować ją również nad polem klasy, wtedy zostanie wygenerowany jedynie getter dla konkretnego pola.

import lombok.Getter;

class User {
    @Getter
    private String name;
    private String surname;
}

public class Main {
    public static void main(String[] args) {
        User user = new User();
        System.out.println(user.getName());
    }
}

 

Adnotacja @Setter działa analogicznie do poprzedniej adnotacji. Generuje ona dla nas setter(y) – można zadeklarować ją nad klasą jak i nad polem.

import lombok.Setter;

@Setter
class User {
    private String name;
    private String surname;
}

public class Main {
    public static void main(String[] args) {
        User user = new User();
        user.setName("Pablo");
        user.setSurname("Escobar");
    }
}

Tak jak w przypadku adnotacji odnoszących się do generowania konstruktorów, tak i w przypadku getterów i setterów możemy zdefiniować dla nich modyfikator dostępu. W tym przypadku nie wykorzystujemy parametru access, a jedynie przekazujemy interesującą nas wartość.

@RequiredArgsConstructor
@Getter(AccessLevel.PROTECTED)
@Setter(AccessLevel.PACKAGE)
class User extends BaseEntity {
    private final String name;
    private final String surname;
}

@ToString

Moim zdaniem, niedocenianą funkcjonalnością lomboka jest generowanie metody toString. Muszę przyznać, że przydaje się to w momencie, gdy potrzebujemy logować stan całego obiektu (Lombok nie bierze pod uwagę pól statycznych!), a nie chcemy go po kolei wyciągać przy użyciu getterów. Jest to na tyle przydatne, że podczas developmentu, pola w klasie mogą się często zmieniać, a Lombok zapewnia, że metoda toString zawsze będzie uwzględniała wszystkie istniejące pola w klasie.

import lombok.RequiredArgsConstructor;


@RequiredArgsConstructor
@ToString
class User {
    private final String name;
    private final String surname;
}

public class Main {
    public static void main(String[] args) {
        User pablo = new User("Pablo", "Escobar");
        System.out.println(pablo);
    }
}

Po uruchomieniu powyższego kodu otrzymamy w konsoli taki rezultat:

User(name=Pablo, surname=Escobar)

Warto wspomnieć, że można wskazać Lombokowi pola, które powinny się znaleźć w metodzie toString, a które wręcz powinny być ignorowane.

W momencie, gdy chcemy, aby Lombok brał pod uwagę jedynie wybrane pola, możemy w adnotacji przekazać argument onlyExplicitlyIncluded = true i wtedy w metodzie toString zostaną użyte pola oznaczone adnotacją @ToString.Include.

import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
import lombok.ToString;


@RequiredArgsConstructor
@ToString(onlyExplicitlyIncluded = true)
@EqualsAndHashCode
class User {
    @ToString.Include
    private final String name;
    private final String surname;
}

public class Main {
    public static void main(String[] args) {
        User pablo = new User("Pablo", "Escobar");
        System.out.println(pablo);
    }
}

Jak widać poniżej, Lombok uwzględnił jedynie pole name.

User(name=Pablo)

W sytuacji, gdy chcemy zignorować wybrane pole, wystarczy użyć adnotacji @ToString.Exclude nad nieinteresującym nas polem.

import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
import lombok.ToString;


@RequiredArgsConstructor
@ToString
@EqualsAndHashCode
class User {
    private final String name;
    @ToString.Exclude
    private final String surname;
}

public class Main {
    public static void main(String[] args) {
        User pablo = new User("Pablo", "Escobar");
        System.out.println(pablo);
    }
}

Rezultat będzie identyczny jak w poprzednim przykładzie – Lombok nie uwzględni pola name.

User(name=Pablo)

@EqualsAndHashCode

Bardzo przydatną funkcjonalnością jest generowanie metod equals oraz hashCode. Można zrobić to przy użyciu adnotacji @EqualsAndHashCode. Oczywiście jest to przydatne, gdy chcemy przechowywać nasze obiekty w kolekcji typu Map lub Set. Koniecznie należy wtedy pamiętać o implementacji metod equals i hashCode.

import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;


@RequiredArgsConstructor
@EqualsAndHashCode
class User {
    private final String name;
    private final String surname;
}

public class Main {
    public static void main(String[] args) {
        User pablo = new User("Pablo", "Escobar");
        User pabloClone = new User("Pablo", "Escobar");
        User kowalski = new User("Jan", "Kowalski");
        
        System.out.println("Pablo == PabloClone: " + pablo.equals(pabloClone));
        System.out.println("Pablo == Kowalski: " + pablo.equals(kowalski));
    }
}

Po uruchomieniu powyższego kodu, można stwierdzić, że Lombok poprawnie wygenerował obie metody.

Pablo == PabloClone: true
Pablo == Kowalski: false

Include & Exclude

W przypadku adnotacji @EqualsAndHashCode mamy również do czynienia z możliwością ignorowania lub wybierania jedynie interesujących nas pól. W tym celu wystarczy wykorzystać adnotacje @EqualsAndHashCode.Include oraz @EqualsAndHashCode.Exclude.

Ta pierwsza z nich może być pomocna, gdy chcemy np. porównać nasze obiekty na podstawie pola id.

import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;

import java.util.UUID;


@RequiredArgsConstructor
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
class User {
    @EqualsAndHashCode.Include
    private final String id = UUID.randomUUID().toString();
    private final String name;
    private final String surname;
}

public class Main {
    public static void main(String[] args) {
        User pablo = new User("Pablo", "Escobar");
        User pabloClone = new User("Pablo", "Escobar");
        User kowalski = new User("Jan", "Kowalski");

        System.out.println("Pablo == PabloClone: " + pablo.equals(pabloClone));
        System.out.println("Pablo == Kowalski: " + pablo.equals(kowalski));
    }
}

Jak widać, metoda equals zwraca za każdym razem false. Dzieje się tak, ponieważ każdy nowy obiekt posiada inny wygenerowany ciąg znaków przypisany do pola id.

Pablo == PabloClone: false
Pablo == Kowalski: false

Użycie ignorowania pól prezentuje się nastepująco:

import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;

import java.util.UUID;


@RequiredArgsConstructor
@EqualsAndHashCode
class User {
    private final String id = UUID.randomUUID().toString();
    private final String name;
    private final String surname;
    @EqualsAndHashCode.Exclude
    private final int version = 0;
}

public class Main {
    public static void main(String[] args) {
        User pablo = new User("Pablo", "Escobar");
        User pabloClone = new User("Pablo", "Escobar");
        User kowalski = new User("Jan", "Kowalski");

        System.out.println("Pablo == PabloClone: " + pablo.equals(pabloClone));
        System.out.println("Pablo == Kowalski: " + pablo.equals(kowalski));
    }
}

@EquasAndHashCode, a dziedziczenie

Mówiąc o adnotacji @EqualsAndHashCode należy powiedzieć o jeszcze jednej istotnej rzeczy, czyli o generowaniu metod equals i hashCode, w przypadku, gdy mamy do czynienia z dziedziczeniem. Jeśli klasa, dla której generujemy metody equals i hashCode dziedziczy po innej klasie, Lombok domyślnie nie weźmie pod uwagę implementacji metod equals i hashCode z klasy bazowej. Należy o tym pamiętać, ponieważ może to skutować dziwnymy błędami w aplikacji.

Aby nakazać Lombokowi, żeby brał pod uwagę również klasę bazową, wystarczy przekazać argument callSuper ustawiony na wartość true w adnotacji nad klasą pochodną (czyli tej, która dziedziczy).

import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;

import java.util.UUID;


@RequiredArgsConstructor
@EqualsAndHashCode
class BaseEntity {
    private final String id = UUID.randomUUID().toString();
}

@RequiredArgsConstructor
@EqualsAndHashCode(callSuper = true)
class User extends BaseEntity {
    private final String name;
    private final String surname;
}

public class Main {
    public static void main(String[] args) {
        User pablo = new User("Pablo", "Escobar");
        User pabloClone = new User("Pablo", "Escobar");
        User kowalski = new User("Jan", "Kowalski");

        System.out.println("Pablo == PabloClone: " + pablo.equals(pabloClone));
        System.out.println("Pablo == Kowalski: " + pablo.equals(kowalski));
    }
}

Dzięki tak prostemu zabiegowi metoda equals zwraca false dla obu porównań, ponieważ podczas porównywania obiektów jest również brane pod uwagę pole id z klasy bazowej BaseEntity

Pablo == PabloClone: false
Pablo == Kowalski: false

@Builder

Lombok umożliwia również wygenerowanie dla nas buildera, czyli implementacji wzorca projektowego builder. Jeśli nie jesteś zaznajomiony z tym wzorcem, zachęcam na początek przeczytanie tego artykułu.

W skrócie builder jest przydatnym wzorcem projektowym, gdy konstruktor posiada więcej niż 3 parametry. Konieczność przekazania dużej ilości argumentów, powoduje, że korzystanie z klasy jest trudniejsze (czasami konieczność zajrzenia do klasy), a może nawet spowodować pomyłkę! (wyobraź sobie 4 parametry typu String stojące obok siebie – może się coś nie udać).

Budowniczny jest idealnym lekiem na wyżej opisany problem, a dodatkowo bardzo ułatwia korzystanie np. z zewnętrznego API, gdy dostawca biblioteki praktycznie prowadzi “za rękę” programistę. Warto zajrzeć na jedno z zadań konkursu “Najlepszy Java Developer” i repozytorium z rozwiązaniem – jest tam podany przykład, jak można zbudować API przyjemne w użyciu za pomocą wzorca budowniczego.

Wracając do tematu, użycie adnotacji @Builder jest również proste jak użycie wszystkich poprzednich adnotacji. Jedyne co potrzebujemy to klasy oraz dodania nad nią adnotacji @Builder.

import lombok.Builder;
import lombok.RequiredArgsConstructor;
import lombok.ToString;

import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.List;

@RequiredArgsConstructor
@ToString
class Student {
    private final String name;
    private final String surname;
}

@Builder
@ToString
class Course {
    private final String name;
    private final ZonedDateTime startDate;
    private final String location;
    private final List<Student> participants;
}


public class Main {
    public static void main(String[] args) {
        Course javaCourse = Course.builder()
                .name("Kurs Java od podstaw!")
                .location("Warsaw")
                .startDate(ZonedDateTime.of(2021, 5, 10, 8, 0, 0, 0, ZoneId.of("Europe/Warsaw")))
                .participants(Arrays.asList(
                        new Student("Pablo", "Escobar"),
                        new Student("Jan", "Kowalski")
                ))
                .build();
        System.out.println(javaCourse);
    }
}



Dzięki użyciu adnotacji @Builder, Lombok wygeneruje dla nas nastepujące rzeczy w klasie:

  • statyczną klasę z sufiksem Builder, która zawiera całą logikę odpowiedzialną za builder np. CourseBuilder,
  • statyczną metodę builder, która zwraca nam instancję buildera np. Course.builder(),
  • metodę build w klasie buildera, która tworzy dla nas instancję naszej klasy np. Course.builder()[…].builder(),
  • metody w klasie builder, które umożliwiają ustawianie wartości każdego z pól np. Course.builder().name(“Kurs Java”)[…].build()

Jak widać, w powyższym przykładzie, bibliotece Lombok zawdzięczamy przyjemne tworzenie obiektu Course, krok po kroku. Aż miło się tego używa.

@Singular

Powyższy kod można jeszcze trochę ulepszyć – jak zauważyłeś wypełnianie listy participants nie jest zbyt wygodne, jednak można to usprawnić adnotacją @Singular na polu, która spowoduje, że elementy do kolekcji będą dodawane jeden po drugim w taki o to sposób:

import lombok.Builder;
import lombok.RequiredArgsConstructor;
import lombok.Singular;
import lombok.ToString;

import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.List;

@RequiredArgsConstructor
@ToString
class Student {
    private final String name;
    private final String surname;
}

@Builder
@ToString
class Course {
    private final String name;
    private final ZonedDateTime startDate;
    private final String location;
    @Singular
    private final List<Student> participants;
}


public class Main {
    public static void main(String[] args) {
        Course javaCourse = Course.builder()
                .name("Kurs Java od podstaw!")
                .location("Warsaw")
                .startDate(ZonedDateTime.of(2021, 5, 10, 8, 0, 0, 0, ZoneId.of("Europe/Warsaw")))
                .participant(new Student("Jan", "Kowalski"))
                .participant(new Student("Jan", "Kowalski"))
                .build();
        System.out.println(javaCourse);
    }
}

Jak widać, nie musimy już męczyć się z używaniem np. Arrays.asList, aby przekazać listę obiektów.

@Builder nad metodą lub konstruktorem

Na koniec warto wspomnieć jeszcze o dwóch rzeczach – jedna z nich to, że adnotacji @Builder możemy używać na metodach oraz konstruktorach. Dzięki temu, na podstawie parametrów metody/konstruktora zostanie wygenerowany builder.

import lombok.Builder;
import lombok.RequiredArgsConstructor;
import lombok.Singular;
import lombok.ToString;

import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.List;

@RequiredArgsConstructor
@ToString
class Student {
    private final String name;
    private final String surname;
}

@ToString
class Course {
    private final String name;
    private final ZonedDateTime startDate;
    private final String location;
    @Singular
    private final List<Student> participants;


    @Builder
    public Course(String name, String location, List<Student> participants) {
        this.name = name;
        this.startDate = ZonedDateTime.of(2021, 5, 10, 8, 0, 0, 0, ZoneId.of("Europe/Warsaw"));
        this.location = location;
        this.participants = participants;
    }
}


public class Main {
    public static void main(String[] args) {
        Course javaCourse = Course.builder()
                .name("Kurs Java od podstaw!")
                .location("Warsaw")
                .participants(Arrays.asList(new Student("Pablo", "Escobar"), new Student("Jan", "Kowalski")))
                .build();
        System.out.println(javaCourse);
    }
}

Takie użycie pozwala nam bardziej edytować proces tworzenia obiektu np. ręczna inicjalizacja konkretnego pola w konstruktorze. Użycie w taki sposób adnotacji @Builder powoduje, jednak brak możliwości użycia funkcjonalności adnotacji @Singular.

@Builder a pola final

Drugą istotną rzeczą, o której trzeba wspomnieć to połączenie buildera wraz z polami finalnymi. Niestety builder wygenerowany przez Lomboka nie wymaga uzupełnienia wszystkich pól, a zatem mogą zostać pominięte final, o których przypomniałby nam konstruktor.

import lombok.Builder;
import lombok.RequiredArgsConstructor;
import lombok.Singular;
import lombok.ToString;

import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.List;

@RequiredArgsConstructor
@ToString
class Student {
    private final String name;
    private final String surname;
}

@ToString
@Builder
class Course {
    private final String name;
    private final ZonedDateTime startDate;
    private final String location;
    @Singular
    private final List<Student> participants;
}


public class Main {
    public static void main(String[] args) {
        Course javaCourse = Course.builder()
                .name("Kurs Java od podstaw!")
                .build();
        System.out.println(javaCourse);
    }
}

Powyższy kod działa bez zarzutu, jednak jak widać zostało uzupełnione jedynie pole name, a reszta pól ma wartość null (oprócz participants – jest on pustą tablicą).

@NonNull

Można jednak się trochę przed tym obronić, używając adnotacji @NonNull. Adnotacja ta w czasie działania aplikacji sprawdzi wartość argumentu, która trafi do konstruktora poprzez wygenerowany builder.

import lombok.*;

import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.List;

@RequiredArgsConstructor
@ToString
class Student {
    private final String name;
    private final String surname;
}

@ToString
@Builder
class Course {
    @NonNull private final String name;
    @NonNull private final ZonedDateTime startDate;
    @NonNull private final String location;
    @Singular
    private final List<Student> participants;
}


public class Main {
    public static void main(String[] args) {
        Course javaCourse = Course.builder()
                .name("Kurs Java od podstaw!")
                .build();
        System.out.println(javaCourse);
    }
}

Automatycznie jest rzucany NullPointerException.  Problem tkwi w tym, że dzieje się to dopiero w Runtime, a nie na etapie kompilacji kodu.

Exception in thread "main" java.lang.NullPointerException: startDate is marked non-null but is null
  at Course.<init>(Main.java:16)
  at Course$CourseBuilder.build(Main.java:16)
  at Main.main(Main.java:33)

Podsumowanie

I to na tyle przygody z tym świetnym narzędziem. Pomimo tego, że Lombok ma trochę wad to i tak jest świetnym narzędziem, które potrafi usprawnić pracę programisty. Zachęcam gorąco do jego używania i poznawania go na wylot. Mi zostało do poznania jeszcze kilka nieznanych parametrów niektórych adnotacji – nie zawsze przydatnych w codziennym użyciu.

Jestem ciekaw, czy na co dzień używasz Lomboka w swojej pracy lub prywatnych projektach. A może jednak słyszysz o nim pierwszy raz i odmieni on Twoje programistyczne życie? Daj znać w komentarzu – chętnie też usłyszę o ciekawostkach związanych z tym narzędziem.

Kamil Klimek

Od 2016 jestem programistą Java. Przez pierwsze 4 lata pracowałem jako Full Stack Java Developer. Później postanowiłem postawić nacisk na Javę, żeby jeszcze lepiej ją poznać.

Subscribe
Powiadom o
guest
0 komentarzy
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x