Aktualizacja kwiecień 2021: Proszę mieć na względzie, iż poniższy wpis dotyczy Javy 15, gdzie rekordy były preview feature. Od tego czasu parę rzeczy się zmieniło, aktualizacja jest w innym wpisie.
Rekordy: konfrontacja
W czasie moich aktywności online dotyczących tłumaczenia rekordów parę osób rzuciło podkręconą piłkę “no a Lombok?”
W szczególności pytanie przybiera formę “skoro rekordy są immutable, to czym się różnią od @Value z Lomboka?”
Przede wszystkim trzeba pamiętać, iż “rekordy nie są Java Beans”, @Value generuje nam niezmienialny (“niemutowalny”) JavaBean.
Częściowo efekty zastosowania record i @Value pokrywają się.
- Pojawią się metody equals(), hashCode(), toString().
- Wszystkie pola zostaną oznaczone jako private final.
- Pojawią się “konstruktory do wszystkich pól” (jeśli w ziarenku od @Value któreś z pól jest zainicjalizowane przy deklaracji, to oczywiście zostanie pominięte).
- record i @Value tworzą klasy, które są final, więc nie można po nich dziedziczyć.
Dwie fundamentalne różnice są takie:
- W rekordach deklarujemy składowe w nagłówku rekordu (pięknie opisuje to gramatyka z JEPa 384) i na tej podstawie generowane są pola i parametry konstruktora kanonicznego, zaś do JavaBean poddanych @Value trzeba wpisać wszystkie pola i to na ich podstawie jest tworzony konstruktor “kanoniczny”.
- Rekordy posiadają “akcesory”, @Value da nam “gettery”.
Różnic oczywiście jest więcej. jeżeli chcecie je dokładnie poznać, to sposobem totalnym można porównywać kod bajtowy (bytecode), lub chociaż dokonać “delombokizacji” @Value w IDE. Przykładowo, jeżeli do konfiguracji Lomboka w lombok.config dopisać lombok.anyConstructor.addConstructorProperties=true, to konstruktor “kanoniczny” dzięki @Value posiada także adnotację @java.beans.ConstructorProperties. “E tam, jakiś detal”. Tak, ale ten detal powoduje, iż elegancko można zdeserializować takim konstruktorem JSONa wykorzystując Jacksona. Jako “elegancko” rozumiem bez pisania @JsonCreator przed konstruktorem i @JsonProperty("name) przed każdym parametrem konstruktora. (Pamiętajmy, iż “normalna deserializacja”, czyli z formatu binarnego, w rekordach też jest robiona przez konstruktor, więc koncepcja deserializacji konstruktorem zyskuje na popularności na wielu frontach. Wreszcie.)
W przyszłości tych różnic może być jeszcze więcej. Jednym ze sposobów na wykorzystanie rekordów ma być dopasowanie wzorców (pattern matching) z wyłuskiwaniem danych. A takie wyłuskiwanie wymaga dekonstrukcji obiektu (jak unapply w Scali). Kiedyś może się okazać, iż record może tworzyć takie dekonstruktory dla rekordów. Na razie brak mi wiedzy, czy i jak będzie można rozkładać obiekty samemu i/lub czy Lombok jakoś to umożliwi.
A teraz: pocałujcie się!
Uważny czytelnik dokumentacji @Value (bo czytasz dokumentację, prawda?) zauważy, iż w jakimś sensie @Value jest skrótem kilku innych adnotacji. Lista wygląda tak: final @ToString @EqualsAndHashCode @AllArgsConstructor @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) @Getter
Rekordy mają toString(), to nie potrzebują @ToString itd. W zasadzie poza @Getter to mają wszystko.
Co się stanie, jeżeli do definicji rekordu dopisać @Getter
Czyli poza akcesorami do składowych, w rekordzie pojawiły mi się również gettery. Nie wiem, czy mi się to podoba. Ale wiem, iż dzięki temu pojawia się kolejna opcja, którą można dołączyć do listy z wpisu o refleksji rekordów i mieć gettery wygenerowane, zamiast dopisywać je manualnie, bo “niestety, ta biblioteka wymaga getterów”.
No ale coś tam pisałem o działaniu @AllArgsConstructor… Jak pomęczyć rekordy jeszcze bardziej i nad klasą dodać tę właśnie adnotację (i gdy mamy odpowiedni wpis w json.config):
to po całej kompilacji konstruktor wygląda następująco:
Co nam to daje? “Kompatybilność” z nieszczęsnym Jacksonem przy przerabianiu JSONa na rekordy (czyli deserializacji).
Podsumowując: dopisanie do definicji rekordu dwóch adnotacji @Getter @AllArgsConstructor przy posiadaniu odpowiednio skonfigurowanego Lomboka w projekcie, już teraz umożliwi nam wykorzystanie rekordów w dość popularnym obszarze, czyli przerabianiu obiektów/rekordów Javy na odpowiedzi RESTowe i wczytywanie tych obiektów/rekordów z żądań RESTowych.
Czy warto?
Szczerze? Nie wiem.
Z jednej strony umożliwi to już teraz (jeśli ktoś się nie boi --enable-preview i lubi życie na krawędzi) wykorzystanie rekordów w bardzo popularnym zakresie. A gdy już wszystkie biblioteki wykorzystywane przez nas będą w stanie obsługiwać rekordy, to będzie można z tych adnotacji zrezygnować.
Z drugiej strony Lombok nie jest za darmo. Ciężko się żyje bez wtyczki do IDE, wtyczka nie zawsze jest aktualna, Lombok (z racji bycia “wtyczką do kompilatora”) nie zadziała np. przy mieszanej kompilacji ze Scalą itd. Na duży minus zasługuje to, iż w moich obecnych ustawieniach (Intellij IDEA 2020.1.1, wtyczka Lombokowa do IDEI: 0.30-2020.1) nie da się wywołać konstruktora rekordu, jeżeli ten konstruktor jest z Lomboka ;-) Kompilowanie i uruchamianie Mavenem na OpenJDK Runtime Environment (build 15-ea+23-1098) jak najbardziej działa.
Czyli móc, to można. Ale czy warto? Każdy niech odpowie sobie sam.
A co z @Value
“No dobrze, ale skoro @Getter @AllArgsConstructor są w jakimś sensie podzbiorem @Value, to może korzystać z @Value przy rekordach?”
Tego bym raczej nie robił w żadnych okolicznościach.
- Wykorzystanie @Value generuje w IDE ostrzeżenie, iż metody equals() i hashCode() nie uwzględniają klas, z których nasza klasa dziedziczy. Na pierwszy rzut oka @Value record ARecord(){} z niczego nie dziedziczy, ale trzeba pamiętać o java.lang.Record. Lombok, zdaje się, ma tu (jeszcze?) problemy.
- Równoczesne wykorzystanie Lomboka i rekordów powoduje, iż jeżeli jakaś metoda jest generowana przez Lombok i przez rekord “z definicji”, to w pliku .class znajdzie się metoda wygenerowana przez Lombok. A tak sobie myślę, iż chyba lepiej zostawić by było tę “oryginalną od rekordu”. Kwestia smaku i kompatybilności w przód.
Lombok jest potężnym narzędziem. I jak każde potężne narzędzie wymaga zrozumienia i odpowiedzialności za każde (złe) użycie.
Jeśli świerzbią kogoś palce, żeby własnoręcznie popastwić się nieco nad rekordami, to kod leży sobie na Githubie.