Napisz beztłuszczowy kod Java z Project Lombok

Opublikowany: 2022-03-11

Istnieje wiele narzędzi i bibliotek, bez których nie wyobrażam sobie pisania kodu w Javie. Tradycyjnie rzeczy takie jak Google Guava czy Joda Time (przynajmniej w erze przed Javą 8) należą do zależności, które wrzucam do moich projektów przez większość czasu, niezależnie od konkretnej domeny.

Lombok z pewnością zasługuje na swoje miejsce również w moich programach POM lub Gradle, aczkolwiek nie jest typowym narzędziem bibliotecznym/frameworkowym. Lombok istnieje już od dłuższego czasu (po raz pierwszy wydany w 2009 roku) i od tego czasu bardzo dojrzał. Jednak zawsze uważałem, że zasługuje na więcej uwagi — to niesamowity sposób radzenia sobie z naturalną gadatliwością Javy.

W tym poście zbadamy, co sprawia, że ​​Lombok jest tak przydatnym narzędziem.

Projekt Lombok

Java ma wiele zalet poza samą JVM, która jest niezwykłym oprogramowaniem. Java jest dojrzała i wydajna, a społeczność i ekosystem wokół niej są ogromne i żywe.

Jednak jako język programowania Java ma pewne własne cechy charakterystyczne, a także opcje projektowe, które sprawiają, że jest dość gadatliwa. Dodaj kilka konstrukcji i wzorców klas, których programiści Java często potrzebujemy, a często otrzymujemy wiele wierszy kodu, które wnoszą niewielką lub żadną rzeczywistą wartość poza przestrzeganiem pewnego zestawu ograniczeń lub konwencji frameworka.

Tutaj do gry wkracza Lombok. Pozwala nam to drastycznie zmniejszyć ilość kodu „boilerplate”, który musimy napisać. Twórcy Lombok to para bardzo mądrych facetów iz pewnością mają zamiłowanie do humoru — nie można przegapić tego intro, które przygotowali na poprzedniej konferencji!

Zobaczmy, jak Lombok robi swoją magię i zobaczmy kilka przykładów użycia.

Jak działa Lombok

Lombok działa jak procesor adnotacji, który „dodaje” kod do twoich klas w czasie kompilacji. Przetwarzanie adnotacji to funkcja dodana do kompilatora Java w wersji 5. Pomysł polega na tym, że użytkownicy mogą umieszczać procesory adnotacji (napisane przez siebie lub poprzez zależności innych firm, takie jak Lombok) w ścieżce klas kompilacji. Następnie, gdy proces kompilacji trwa, za każdym razem, gdy kompilator znajdzie adnotację, pyta: „Hej, czy ktoś w ścieżce klas jest zainteresowany tą @Adnotacją?”. Dla tych procesorów, którzy podnoszą ręce, kompilator przekazuje im kontrolę wraz z kontekstem kompilacji, aby mogli… cóż… przetworzyć.

Być może najczęstszym przypadkiem procesorów adnotacji jest generowanie nowych plików źródłowych lub przeprowadzanie pewnego rodzaju kontroli w czasie kompilacji.

Lombok tak naprawdę nie należy do tych kategorii: To, co robi, to modyfikowanie struktur danych kompilatora używanych do reprezentowania kodu; tj. jego abstrakcyjne drzewo składni (AST). Modyfikując AST kompilatora, Lombok pośrednio zmienia samo końcowe generowanie kodu bajtowego.

To niezwykłe i dość nachalne podejście tradycyjnie powodowało, że Lombok był postrzegany jako coś w rodzaju hacka. Chociaż sam zgodziłbym się do pewnego stopnia z tą charakterystyką, zamiast postrzegać to w złym znaczeniu tego słowa, postrzegałbym Lombok jako „sprytną, technicznie wartościową i oryginalną alternatywę”.

Mimo to są programiści, którzy uważają to za włamanie i z tego powodu nie używają Lomboka. Jest to zrozumiałe, ale z mojego doświadczenia wynika, że ​​korzyści związane z produktywnością Lombok przewyższają wszelkie z tych obaw. Od wielu lat z radością wykorzystuję go do projektów produkcyjnych.

Zanim przejdę do szczegółów, chciałbym podsumować dwa powody, dla których szczególnie cenię wykorzystanie Lomboka w moich projektach:

  1. Lombok pomaga utrzymać mój kod czysty, zwięzły i rzeczowy. Uważam, że moje zajęcia z adnotacjami Lombok są bardzo ekspresyjne i ogólnie uważam, że kod z adnotacjami jest dość ujawniający intencje, chociaż nie wszyscy w Internecie muszą się z tym zgodzić.
  2. Kiedy zaczynam projekt i myślę o modelu domeny, zwykle zaczynam od pisania zajęć, które są bardzo w toku i które zmieniam iteracyjnie, gdy myślę dalej i udoskonalam je. Na tych wczesnych etapach Lombok pomaga mi poruszać się szybciej, ponieważ nie musi poruszać się ani przekształcać kodu, który dla mnie generuje.

Wzór fasoli i metody wspólnych obiektów

Wiele narzędzi i frameworków Java, których używamy, opiera się na wzorcu fasoli. Java Beans to klasy, które można serializować, które mają domyślny konstruktor z zerowymi argumentami (i prawdopodobnie inne wersje) i ujawniają swój stan za pomocą programów pobierających i ustawiających, zazwyczaj wspieranych przez pola prywatne. Piszemy ich wiele, na przykład podczas pracy z frameworkami JPA lub serializacji, takimi jak JAXB lub Jackson.

Rozważ ten bean użytkownika, który przechowuje do pięciu atrybutów (właściwości), dla których chcielibyśmy mieć dodatkowy konstruktor dla wszystkich atrybutów, sensowną reprezentację ciągu i zdefiniować równość/haszowanie pod względem pola e-mail:

 public class User implements Serializable { private String email; private String firstName; private String lastName; private Instant registrationTs; private boolean payingCustomer; // Empty constructor implementation: ~3 lines. // Utility constructor for all attributes: ~7 lines. // Getters/setters: ~38 lines. // equals() and hashCode() as per email: ~23 lines. // toString() for all attributes: ~3 lines. // Relevant: 5 lines; Boilerplate: 74 lines => 93% meaningless code :( }

Dla zwięzłości w tym miejscu, zamiast uwzględniać rzeczywistą implementację wszystkich metod, zamiast tego przedstawiłem tylko komentarze z listą metod i liczbą wierszy kodu, które zajęły rzeczywiste implementacje. Ten standardowy kod stanowiłby łącznie ponad 90% kodu dla tej klasy!

Co więcej, gdybym później chciał, powiedzmy, zmienić email na adres e-mail lub aby registrationTs była Date zamiast emailAddress , Instant poświęcić czas (z pomocą mojego IDE w niektórych przypadkach, co prawda) na zmianę rzeczy, takich jak get /set nazwy i typy metod, zmodyfikuj mój konstruktor narzędziowy i tak dalej. Ponownie, bezcenny czas na coś, co nie wnosi praktycznej wartości biznesowej do mojego kodu.

Zobaczmy, jak Lombok może tu pomóc:

 import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; @Getter @Setter @NoArgsConstructor @AllArgsConstructor @ToString @EqualsAndHashCode(of = {"email"}) public class User { private String email; private String firstName; private String lastName; private Instant registrationTs; private boolean payingCustomer; }

Voila! Właśnie dodałem kilka adnotacji lombok.* i osiągnąłem dokładnie to, czego chciałem. Powyższa lista to dokładnie cały kod, który potrzebuję do tego napisać. Lombok podłącza się do mojego procesu kompilatora i wygenerował wszystko dla mnie (patrz zrzut ekranu poniżej mojego IDE).

Zrzut ekranu IDE

Jak zauważyłeś, inspektor NetBeans (i stanie się to niezależnie od IDE) wykrywa skompilowany kod bajtowy klasy, w tym dodatki wprowadzone do procesu przez Lomboka. To, co się tutaj wydarzyło, jest całkiem proste:

  • Używając @Getter i @Setter poinstruowałem Lomboka, aby generował gettery i settery dla wszystkich atrybutów. To dlatego, że użyłem adnotacji na poziomie klasy. Gdybym chciał selektywnie określić, co generować dla jakich atrybutów, mógłbym dodać adnotacje do samych pól.
  • Dzięki @NoArgsConstructor i @AllArgsConstructor otrzymałem domyślny pusty konstruktor dla mojej klasy oraz dodatkowy dla wszystkich atrybutów.
  • Adnotacja @ToString automatycznie generuje przydatną metodę toString() , wyświetlając domyślnie wszystkie atrybuty klasy poprzedzone ich nazwą.
  • Na koniec, aby zdefiniować parę metod equals() i hashCode() pod kątem pola email, użyłem @EqualsAndHashCode i sparametryzowałem go listą odpowiednich pól (w tym przypadku tylko email).

Dostosowywanie adnotacji Lombok

Użyjmy teraz kilku dostosowań Lombok na podstawie tego samego przykładu:

  • Chciałbym zmniejszyć widoczność domyślnego konstruktora. Ponieważ potrzebuję go tylko ze względu na zgodność z fasolą, oczekuję, że konsumenci klasy będą wywoływać tylko konstruktora, który przyjmuje wszystkie pola. Aby to wymusić, dostosowuję wygenerowany konstruktor za pomocą AccessLevel.PACKAGE .
  • Chcę mieć pewność, że moje pola nigdy nie otrzymają wartości null, ani za pośrednictwem konstruktora, ani za pomocą metod ustawiających. Wystarczy adnotacja atrybutów klasy za pomocą @NonNull ; Lombok wygeneruje kontrole o wartości NULL, rzucając NullPointerException , gdy jest to właściwe w metodach konstruktora i ustawiacza.
  • Dodam atrybut password , ale nie chcę, aby był pokazywany podczas wywoływania toString() ze względów bezpieczeństwa. Jest to realizowane za pomocą argumentu @ToString .
  • Nie mam nic przeciwko ujawnianiu stanu publicznie za pomocą programów pobierających, ale wolałbym ograniczyć zmienność na zewnątrz. Więc zostawiam @Getter bez zmian, ale ponownie używam AccessLevel.PROTECTED dla @Setter .
  • Być może chciałbym wymusić pewne ograniczenie w polu email , aby w przypadku jego modyfikacji uruchomiono jakiś rodzaj kontroli. W tym celu sam implementuję metodę setEmail() . Lombok po prostu pominie generowanie metody, która już istnieje.

Tak będzie wtedy wyglądała klasa User:

 import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.NonNull; import lombok.Setter; import lombok.ToString; @Getter @Setter(AccessLevel.PROTECTED) @NoArgsConstructor(access = AccessLevel.PACKAGE) @AllArgsConstructor @ToString(exclude = {"password"}) @EqualsAndHashCode(of = {"email"}) public class User { private @NonNull String email; private @NonNull byte[] password; private @NonNull String firstName; private @NonNull String lastName; private @NonNull Instant registrationTs; private boolean payingCustomer; protected void setEmail(String email) { // Check for null (=> NullPointerException) // and valid email code (=> IllegalArgumentException) this.email = email; } }

Zauważ, że dla niektórych adnotacji określamy atrybuty klasy jako zwykłe ciągi. Nie ma problemu, ponieważ Lombok zgłosi błąd kompilacji, jeśli na przykład źle wpiszemy lub odwołamy się do nieistniejącego pola. Z Lombokiem jesteśmy bezpieczni.

Podobnie jak w przypadku metody setEmail() , Lombok będzie po prostu OK i nie wygeneruje niczego dla metody już zaimplementowanej przez programistę. Dotyczy to wszystkich metod i konstruktorów.

Niezmienne struktury danych

Innym przypadkiem użycia, w którym Lombok przoduje, jest tworzenie niezmiennych struktur danych. Są one zwykle określane jako „typy wartości”. Niektóre języki mają wbudowaną obsługę tych języków i istnieje nawet propozycja włączenia tego do przyszłych wersji Javy.

Załóżmy, że chcemy zamodelować odpowiedź na akcję logowania użytkownika. Jest to rodzaj obiektu, który chcielibyśmy po prostu utworzyć i powrócić do innych warstw aplikacji (na przykład do serializacji JSON jako treści odpowiedzi HTTP). Taka odpowiedź logowania w ogóle nie musiałaby być zmienna, a Lombok może pomóc to zwięźle opisać. Oczywiście, istnieje wiele innych przypadków użycia niezmiennych struktur danych (są przyjazne dla wielowątkowości i pamięci podręcznej, między innymi), ale trzymajmy się tego prostego przykładu:

 import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.ToString; import lombok.experimental.Wither; @Getter @RequiredArgsConstructor @ToString @EqualsAndHashCode public final class LoginResponse { private final long userId; private final @NonNull String authToken; private final @NonNull Instant loginTs; @Wither private final @NonNull Instant tokenExpiryTs; }

Warto tutaj zwrócić uwagę:

  • Wprowadzono adnotację @RequiredArgsConstructor . Trafnie nazwany, generuje konstruktor dla wszystkich końcowych pól, które nie zostały jeszcze zainicjowane.
  • W przypadkach, w których chcemy ponownie wykorzystać wcześniej wystawiony LoginResonse (wyobraźmy sobie np. operację „odśwież token”) na pewno nie chcemy modyfikować naszej istniejącej instancji, a raczej na jej podstawie wygenerować nową. . Zobacz, w jaki sposób adnotacja @Wither pomaga nam tutaj: mówi Lombok, aby wygenerował withTokenExpiryTs(Instant tokenExpiryTs) , która tworzy nową instancję LoginResponse zawierającą wszystkie wartości instancji with'ed, z wyjątkiem nowej, którą określamy. Czy chcesz to zachowanie dla wszystkich pól? Zamiast tego dodaj @Wither do deklaracji klasy.

@Dane i @Wartość

Oba omówione do tej pory przypadki użycia są tak powszechne, że Lombok dostarcza kilka adnotacji, aby były jeszcze krótsze: Adnotowanie klasy za pomocą @Data spowoduje, że Lombok będzie zachowywał się tak, jakby był opatrzony adnotacjami @Getter + @Setter + @ToString + @EqualsAndHashCode + @RequiredArgsConstructor . Podobnie, użycie @Value zmieni twoją klasę w niezmienną (i ostateczną), również ponownie tak, jakby była opisana powyższą listą.

Wzór budowniczego

Wracając do naszego przykładu User, jeśli chcemy utworzyć nową instancję, będziemy musieli użyć konstruktora z maksymalnie sześcioma argumentami. To już dość duża liczba, która będzie jeszcze gorsza, jeśli do klasy dodamy kolejne atrybuty. Załóżmy również, że chcielibyśmy ustawić kilka domyślnych wartości dla pól lastName i payingCustomer .

Lombok implementuje bardzo potężną funkcję @Builder , która pozwala nam używać wzorca Builder do tworzenia nowych instancji. Dodajmy to do naszej klasy User:

 import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.NonNull; import lombok.Setter; import lombok.ToString; @Getter @Setter(AccessLevel.PROTECTED) @NoArgsConstructor(access = AccessLevel.PACKAGE) @AllArgsConstructor @ToString(exclude = {"password"}) @EqualsAndHashCode(of = {"email"}) @Builder public class User { private @NonNull String email; private @NonNull byte[] password; private @NonNull String firstName; private @NonNull String lastName = ""; private @NonNull Instant registrationTs; private boolean payingCustomer = false; }

Teraz jesteśmy w stanie płynnie tworzyć nowych użytkowników takich jak:

 User user = User .builder() .email("[email protected]") .password("secret".getBytes(StandardCharsets.UTF_8)) .firstName("Miguel") .registrationTs(Instant.now()) .build();

Łatwo sobie wyobrazić, jak wygodny staje się ten konstrukt w miarę rozwoju naszych klas.

Delegacja/skład

Jeśli chcesz postępować zgodnie z bardzo rozsądną zasadą „faworyzowania kompozycji nad dziedziczeniem”, Java tak naprawdę nie pomaga, jeśli chodzi o gadatliwość. Jeśli chcesz komponować obiekty, zazwyczaj musisz napisać wywołania metod delegowania w całym miejscu.

Lombok proponuje rozwiązanie tego problemu za pośrednictwem @Delegate . Spójrzmy na przykład.

Wyobraź sobie, że chcemy wprowadzić nową koncepcję ContactInformation . Oto niektóre informacje, które posiada nasz User i możemy chcieć, aby posiadały je również inne klasy. Możemy to następnie zamodelować za pomocą interfejsu takiego jak ten:

 public interface HasContactInformation { String getEmail(); String getFirstName(); String getLastName(); }

Następnie wprowadzilibyśmy nową klasę ContactInformation przy użyciu Lombok:

 import lombok.Data; @Data public class ContactInformation implements HasContactInformation { private String email; private String firstName; private String lastName; }

I na koniec, moglibyśmy zrefaktoryzować User , aby komponował z ContactInformation i użyć Lomboka do wygenerowania wszystkich wymaganych wywołań delegowania, aby pasowały do ​​umowy interfejsu:

 import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.NonNull; import lombok.Setter; import lombok.ToString; import lombok.experimental.Delegate; @Getter @Setter(AccessLevel.PROTECTED) @NoArgsConstructor(access = AccessLevel.PACKAGE) @AllArgsConstructor @ToString(exclude = {"password"}) @EqualsAndHashCode(of = {"contactInformation"}) public class User implements HasContactInformation { @Getter(AccessLevel.NONE) @Delegate(types = {HasContactInformation.class}) private final ContactInformation contactInformation = new ContactInformation(); private @NonNull byte[] password; private @NonNull Instant registrationTs; private boolean payingCustomer = false; }

Zauważ, że nie musiałem pisać implementacji dla metod HasContactInformation : to jest coś, co mówimy Lombokowi, delegując wywołania do naszej instancji ContactInformation .

Ponadto, ponieważ nie chcę, aby delegowana instancja była dostępna z zewnątrz, dostosowuję ją za pomocą @Getter(AccessLevel.NONE) , skutecznie uniemożliwiając jej generowanie.

Sprawdzone wyjątki

Jak wszyscy wiemy, Java rozróżnia wyjątki sprawdzone i niesprawdzone. Jest to tradycyjne źródło kontrowersji i krytyki języka, ponieważ w rezultacie obsługa wyjątków czasami staje się zbyt trudna, szczególnie gdy mamy do czynienia z interfejsami API zaprojektowanymi do rzucania sprawdzonych wyjątków, a zatem zmuszając nas, programistów do ich przechwycenia lub zadeklarowania naszych metod Wyrzucić ich.

Rozważ ten przykład:

 public class UserService { public URL buildUsersApiUrl() { try { return new URL("https://apiserver.com/users"); } catch (MalformedURLException ex) { // Malformed? Really? throw new RuntimeException(ex); } } }

Jest to taki powszechny wzorzec: z pewnością wiemy, że nasz adres URL jest dobrze sformułowany, ale — ponieważ konstruktor URL zgłasza sprawdzony wyjątek — jesteśmy albo zmuszeni do przechwycenia go, albo zadeklarowania naszej metody, aby go wyrzucić i wyrzucić wywołujący w tej samej sytuacji. Zawijanie tych sprawdzonych wyjątków w RuntimeException jest bardzo rozszerzoną praktyką. A to jeszcze gorzej, jeśli liczba sprawdzanych wyjątków, z którymi musimy się uporać, rośnie w miarę kodowania.

Więc to jest dokładnie to, do czego służy Lombok @SneakyThrows , zawija wszystkie sprawdzone wyjątki, które mają zostać wrzucone w naszej metodzie, w niesprawdzoną i uwolni nas od kłopotów:

 import lombok.SneakyThrows; public class UserService { @SneakyThrows public URL buildUsersApiUrl() { return new URL("https://apiserver.com/users"); } }

Logowanie

Jak często dodajesz instancje rejestratora do swoich klas w ten sposób? (próbka SLF4J)

 private static final Logger LOG = LoggerFactory.getLogger(UserService.class);

Trochę zgaduję. Wiedząc o tym, twórcy Lomboka zaimplementowali adnotację, która tworzy instancję rejestratora z konfigurowalną nazwą (domyślnie log), obsługując najpopularniejsze struktury rejestrowania na platformie Java. Po prostu tak (ponownie, oparty na SLF4J):

 import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; @Slf4j public class UserService { @SneakyThrows public URL buildUsersApiUrl() { log.debug("Building users API URL"); return new URL("https://apiserver.com/users"); } }

Adnotowanie wygenerowanego kodu

Jeśli użyjemy Lombok do generowania kodu, może się wydawać, że stracimy możliwość dodawania adnotacji do tych metod, ponieważ w rzeczywistości ich nie piszemy. Ale to nie jest prawda. Lombok pozwala nam raczej powiedzieć, jak chcielibyśmy, aby wygenerowany kod był opatrzony adnotacjami, używając jednak nieco osobliwej notacji, prawdę mówiąc.

Rozważmy ten przykład, ukierunkowany na użycie struktury wstrzykiwania zależności: mamy klasę UserService , która używa iniekcji konstruktora, aby uzyskać odwołania do UserRepository i UserApiClient .

 package com.mgl.toptal.lombok; import javax.inject.Inject; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor(onConstructor = @__(@Inject)) public class UserService { private final UserRepository userRepository; private final UserApiClient userApiClient; // Instead of: // // @Inject // public UserService(UserRepository userRepository, // UserApiClient userApiClient) { // this.userRepository = userRepository; // this.userApiClient = userApiClient; // } }

Powyższy przykład pokazuje, jak dodawać adnotacje do wygenerowanego konstruktora. Lombok pozwala nam zrobić to samo również dla generowanych metod i parametrów.

Uczyć się więcej

Wykorzystanie Lombok wyjaśnione w tym poście koncentruje się na tych funkcjach, które przez lata osobiście uważałem za najbardziej przydatne. Dostępnych jest jednak wiele innych funkcji i dostosowań.

Dokumentacja Lomboka jest bardzo pouczająca i dokładna. Mają dedykowane strony dla każdej funkcji (adnotacji) z bardzo szczegółowymi wyjaśnieniami i przykładami. Jeśli uważasz ten post za interesujący, zachęcam do zagłębienia się w Lombok i jego dokumentację, aby dowiedzieć się więcej.

Witryna projektu dokumentuje, jak używać Lombok w kilku różnych środowiskach programistycznych. Krótko mówiąc, obsługiwane są najpopularniejsze IDE (Eclipse, NetBeans i IntelliJ). Sam regularnie zmieniam jeden projekt na drugi i bezbłędnie używam Lomboka.

Delombok!

Delombok jest częścią „łańcucha narzędzi Lombok” i może się bardzo przydać. Zasadniczo generuje kod źródłowy Java dla kodu z adnotacjami Lombok, wykonując te same operacje, co generowany przez Lombok kod bajtowy.

To świetna opcja dla osób rozważających adopcję Lomboka, ale jeszcze nie do końca pewna. Możesz swobodnie zacząć z niego korzystać i nie będzie „zamrożenia dostawcy”. W przypadku, gdy Ty lub Twój zespół później żałujesz wyboru, zawsze możesz użyć delomboka do wygenerowania odpowiedniego kodu źródłowego, którego możesz następnie użyć bez pozostawania w zależności od Lomboka.

Delombok jest również doskonałym narzędziem do dokładnego poznania tego, co będzie robił Lombok. Są bardzo proste sposoby na podłączenie go do procesu kompilacji.

Alternatywy

W świecie Java istnieje wiele narzędzi, które w podobny sposób wykorzystują procesory adnotacji do wzbogacania lub modyfikowania kodu w czasie kompilacji, takie jak Immutables lub Google Auto Value. Te (i inne, na pewno!) pokrywają się z Lombok pod względem funkcji. Szczególnie podoba mi się podejście Immutables i używam go również w niektórych projektach.

Warto również zauważyć, że istnieją inne świetne narzędzia oferujące podobne funkcje do „wzmacniania kodu bajtowego”, takie jak Byte Buddy lub Javassist. Zazwyczaj działają one jednak w czasie wykonywania i stanowią własny świat poza zakresem tego posta.

Zwięzła Java

Istnieje wiele nowoczesnych języków docelowych JVM, które zapewniają bardziej idiomatyczne — lub nawet na poziomie języka — podejścia do projektowania, pomagając rozwiązać niektóre z tych samych problemów. Z pewnością Groovy, Scala i Kotlin są dobrymi przykładami. Ale jeśli pracujesz nad projektem wyłącznie w języku Java, Lombok jest dobrym narzędziem, które pomaga twoim programom być bardziej zwięzłym, wyrazistym i łatwiejszym w utrzymaniu.

Powiązane: Język Dart: kiedy Java i C# nie są wystarczająco ostre