Rekordy Java to funkcja wprowadzona jako funkcja podglądu w Javie 14 i sfinalizowana później, aby zapewnić zwięzły i wygodny sposób deklarowania klas używanych głównie do przechowywania danych. Zostały zaprojektowane w celu uproszczenia tworzenia niezmiennych klas poprzez automatyczne generowanie niezbędnego kodu do hermetyzacji pól danych, konstruktorów, akcesorów i innych powszechnych metod.
Główne motywacje stojące za wprowadzeniem Java Records to:
Zwięzłość: rekordy redukują standardowy kod, automatycznie generując standardowe implementacje typowych zadań, takich jak metody dostępu do pól oraz metody
equals(),hashCode()itoString(). Dzięki temu programiści mogą skoncentrować się na definiowaniu pól danych i ich zachowania.
Niezmienność: rekordy są domyślnie zaprojektowane tak, aby były niezmienne, co oznacza, że po utworzeniu obiektu nie można zmienić jego stanu. Ta właściwość jest pożądana w wielu scenariuszach, w których ważna jest integralność i spójność danych.
Czytelność: dzięki użyciu rekordów intencja kodu staje się jaśniejsza. Rekordy wyraźnie komunikują, że klasa jest przede wszystkim kontenerem danych z określonym zestawem pól.
Dopasowywanie wzorców: rekordy Java dobrze współpracują z funkcją dopasowywania wzorców wprowadzoną w Javie 14. Można ich używać jako wzorców w celu uproszczenia kodu obejmującego wyodrębnianie i porównywanie danych.
Jak tworzyć rekordy?
Stwórzmy niezmienną klasę:
public final class Account {
private final String name;
private final int id;
private final String type;
public Account(String name, int id, String type) {
this.name = name;
this.id = id;
this.type = type;
}
public String name() {
return name;
}
public int id() {
return id;
}
public String type() {
return type;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Account that = (Account) o;
return id == that.id && Objects.equals(name, that.name) && Objects.equals(type, that.type);
}
@Override
public int hashCode() {
return Objects.hash(name, id, type);
}
@Override
public String toString() {
return "Account{" +
"name='" + name + '\'' +
", id=" + id +
", type='" + type + '\'' +
'}';
}
}
Utwórzmy teraz tę samą niezmienną klasę, używając rekordu. Musimy użyć słowa kluczowego record, aby utworzyć klasę rekordów w Javie. Podobnie jak to robimy w konstruktorach, musimy wspomnieć o atrybutach i ich typach w rekordzie. W podanym przykładzie Konto jest typem rekordu i służy do przechowywania informacji o koncie:
public record Account(String name, int id, String type) {}
Jak widzimy, możemy zredukować całą klasę za pomocą jednej linii kodu. Zastąpiliśmy słowo kluczowe class, aby zamiast tego użyć słowa kluczowego record i pozwoliliśmy, aby zadziałała magia prostoty.
Słowo kluczowe record w Javie automatycznie generuje dla nas kilka metod, w tym konstruktor, gettery, equals(), hashCode() i toString(). Metody te służą do wygodnego tworzenia obiektów klasy rekordów i manipulowania nimi. Przykładowo po zdefiniowaniu rekordu Account możemy utworzyć instancję klasy Account w następujący sposób:
Account account = new Account("John Doe", 123456, "Savings");
Spowoduje to utworzenie nowego obiektu Account z określonymi wartościami pól name, id i type. Ponieważ jest to rekord, możemy uzyskać bezpośredni dostęp do pól za pomocą wygenerowanych metod pobierających. Na przykład:
String accountName = account.name(); int accountId = account.id(); String accountType = account.type(); System.out.println(accountName); System.out.println(accountId); System.out.println(accountType); // Output John Doe 123456 Savings
Ważne punkty:
- Nie możemy jawnie rozszerzyć klasy Record
Chociaż wszystkie rekordy rozszerzają klasę java.lang.Record, nadal nie możemy jawnie utworzyć podklasy java.lang.Record. Kompilator nie przejdzie. Oznacza to, że jedynym sposobem uzyskania rekordu jest jego jawne zadeklarowanie
final class Account extends Record {
// Compiler error : The type Data may not subclass Record explicitly
private final int id;
}
2. Implementowanie interfejsów
Klasy rekordów pozwalają na implementację interfejsów. Możemy zaimplementować dowolny interfejs, niezależnie od tego, czy jest to pojedynczy interfejs, czy wiele interfejsów.
public record Account( int id, String name) implements Runnable, Serializable
3. Nie można zdefiniować własnych zmiennych instancji
Kiedy definiujemy nagłówek, reprezentuje on stan klasy rekordu. Oznacza to, że w rekordzie nie może znajdować się żadna inna zmienna instancji. Jedyna zmienna instancji, która zostałaby utworzona, to ta podana w komponencie nagłówka. Jednakże w rekordach możemy mieć zmienne statyczne, do których można uzyskać dostęp w taki sam sposób jak do klas, używając nazwy klasy rekordów.
4. Wielu konstruktorów
Definicja typu rekordu Java może zawierać wiele konstruktorów. Oto przykład rekordu Java, który definiuje dodatkowy konstruktor dla typu rekordu Konto:
public record Account( int id, String name) {
public Account(int id) {
this(id, null);
}
}
Dodatkowy konstruktor jest zadeklarowany w treści deklaracji rekordu konta. Zwróć uwagę, jak dodatkowy konstruktor wywołuje domyślny konstruktor rekordu konta. Jest to wymagane przez kompilator Java, więc kompilator wie, które parametry konstruktora w dodatkowym konstruktorze odpowiadają parametrom konstruktora domyślnego.
Możemy dodać tyle dodatkowych konstruktorów, ile ma to sens w przypadku naszej konkretnej definicji rekordu Java.
5. Metody instancji
Możemy dodać metody instancji do definicji rekordu Java — tak samo jak w przypadku zwykłej klasy Java. Oto przykład definicji rekordu Java konta z wcześniejszych sekcji z dodaną metodą instancji o nazwie nameAsUpperCase():
public record Account( int id, String name) {
public static String nameAsUpperCase() {
return name().toUpperCase();
}
}
Zwróć uwagę, jak metoda nameAsUperCase() wywołuje wewnętrznie automatycznie wygenerowaną metodę name().
6. Metody statyczne
Możliwe jest również dodanie metod statycznych do definicji rekordu Java. Oto przykład :
public record Account( int id, String name) {
public static String nameAsUpperCase(Account acc) {
return acc.name.toUpperCase();
}
}
7. Korzystanie z adnotacji
Do elementów rekordu możemy dodawać adnotacje. Na przykład możemy zastosować adnotację @Transient do pola id.
public record Account(@Transient Long id,String name, String type)
{ // ... }
8. Serializacja i deserializacja
Serializacja rekordów w Javie różni się od zwykłych klas. Kiedy obiekt rekordu jest serializowany, jego serializowana postać składa się z sekwencji wartości uzyskanych z pól ostatniej instancji obiektu. Format strumienia obiektu rekordu pozostaje taki sam jak zwykłego obiektu w strumieniu.
Podczas deserializacji, jeśli klasa lokalna odpowiadająca podanemu deskryptorowi klasy strumienia jest klasą rekordów, pola strumienia są odczytywane i rekonstruowane w celu reprezentowania wartości komponentów rekordu. Następnie tworzony jest obiekt rekordu poprzez wywołanie konstruktora kanonicznego rekordu z wartościami komponentów jako argumentami. W przypadku braku wartości komponentu w strumieniu używana jest domyślna wartość typu komponentu.
Domyślnie serialVersionUID klasy rekordów to 0L, chyba że zostanie to wyraźnie zadeklarowane. W przypadku klas rekordów zniesiono również wymóg dopasowania wartości serialVersionUID. Nie można dostosowywać procesu serializacji obiektów rekordów. Wszelkie metody specyficzne dla klasy, takie jak writeObject, readObject, readObjectNoData, readResolve, writeExternal i readExternal zdefiniowane w klasach rekordów, są ignorowane podczas serializacji i deserializacji. Można jednak zastosować metodę writeReplace w celu zwrócenia alternatywnego obiektu do serializacji.
Przed wykonaniem jakiejkolwiek serializacji lub deserializacji musimy upewnić się, że rekord musi nadawać się do serializacji lub uzewnętrznienia.
public record Account (...) implements Serializable { }
Wniosek
Typy rekordów w Javie są bardzo korzystną funkcją i stanowią cenne uzupełnienie każdego systemu opartego na Javie. Przyjmując rekordy, liczne aplikacje mogą zwiększyć przejrzystość i zwięzłość klas swoich domen. Co więcej, zespoły mogą wyeliminować potrzebę ręcznie spreparowanych implementacji podstawowego wzorca i potencjalnie zmniejszyć lub wyeliminować poleganie na bibliotekach takich jak Lombok.
Miłej nauki!!