JavaRush /Blog Java /Random-PL /Przerwa kawowa #128. Przewodnik po rekordach Java

Przerwa kawowa #128. Przewodnik po rekordach Java

Opublikowano w grupie Random-PL
Źródło: abhinavpandey.dev W tym samouczku omówimy podstawy korzystania z rekordów w Javie. Rekordy zostały wprowadzone w Javie 14 jako sposób na usunięcie szablonowego kodu związanego z tworzeniem obiektów Value przy jednoczesnym wykorzystaniu obiektów niezmiennych. Przerwa kawowa #128.  Przewodnik po rekordach Java — 1

1. Podstawowe pojęcia

Zanim przejdziemy do samych wpisów, przyjrzyjmy się problemowi, jaki rozwiązują. Aby to zrobić, będziemy musieli pamiętać, jak obiekty wartości były tworzone przed Javą 14.

1.1. Obiekty wartości

Obiekty wartości są integralną częścią aplikacji Java. Przechowują dane, które należy przesłać pomiędzy warstwami aplikacji. Obiekt wartości zawiera pola, konstruktory i metody dostępu do tych pól. Poniżej znajduje się przykład obiektu wartości:
public class Contact {
    private final String name;
    private final String email;

    public Contact(String name, String email) {
        this.name = name;
        this.email = email;
    }

    public String getName() {
        return name;
    }

    public String getEmail() {
        return email;
    }
}

1.2. Równość obiektów wartości

Obiekty wartości mogą również umożliwiać porównywanie ich pod kątem równości. Domyślnie Java porównuje równość obiektów, porównując ich adresy pamięci. Jednak w niektórych przypadkach obiekty zawierające te same dane można uznać za równe. Aby to zaimplementować, możemy zastąpić metody równości i .hashCode . Zaimplementujmy je dla klasy Contact :
public class Contact {

    // ...

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Contact contact = (Contact) o;
        return Object.equals(email, contact.email) &&
                Objects.equals(name, contact.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, email);
    }
}

1.3. Niezmienność obiektów Value

Obiekty wartości muszą być niezmienne. Oznacza to, że musimy ograniczyć możliwości zmiany pól obiektu. Jest to wskazane z następujących powodów:
  • Aby uniknąć ryzyka przypadkowej zmiany wartości pola.
  • Aby zapewnić, że równe przedmioty pozostaną takie same przez całe życie.
Ponieważ klasa Contact jest już niezmienna, możemy teraz:
  1. uczynił pola prywatnymi i ostatecznymi .
  2. dostarczył tylko moduł pobierający dla każdego pola (bez elementów ustawiających ).

1.4. Rejestrowanie obiektów wartościowych

Często musimy zarejestrować wartości zawarte w obiektach. Odbywa się to poprzez udostępnienie metody toString . Za każdym razem, gdy obiekt jest rejestrowany lub drukowany, wywoływana jest metoda toString . Najprostszym sposobem jest wydrukowanie wartości każdego pola. Oto przykład:
public class Contact {
    // ...
    @Override
    public String toString() {
        return "Contact[" +
                "name='" + name + '\'' +
                ", email=" + email +
                ']';
    }
}

2. Zmniejsz liczbę szablonów za pomocą rekordów

Ponieważ większość obiektów wartościowych ma te same potrzeby i funkcjonalność, dobrze byłoby uprościć proces ich tworzenia. Przyjrzyjmy się, jak nagrania pomagają to osiągnąć.

2.1. Konwersja klasy Person na Record

Utwórzmy wpis klasy Kontakt , który będzie miał taką samą funkcjonalność jak zdefiniowana powyżej klasa Kontakt .
public record Contact(String name, String email) {}
Słowo kluczowe record służy do tworzenia klasy Record . Rekordy mogą być przetwarzane przez osobę wywołującą w taki sam sposób, jak klasa. Na przykład, aby utworzyć nową instancję wpisu, możemy użyć słowa kluczowego new .
Contact contact = new Contact("John Doe", "johnrocks@gmail.com");

2.2. Domyślne zachowanie

Zredukowaliśmy kod do jednej linii. Wymieńmy, co obejmuje:
  1. Pola nazwy i adresu e-mail są domyślnie prywatne i ostateczne.

  2. Kod definiuje „konstruktor kanoniczny”, który przyjmuje pola jako parametry.

  3. Pola są dostępne poprzez metody podobne do gettera - name() i email() . Nie ma modułu ustawiającego pola, więc dane w obiekcie stają się niezmienne.

  4. Zaimplementowano metodę toString , aby wydrukować pola, tak jak to zrobiliśmy w przypadku klasy Contact .

  5. Zaimplementowano metody równości i .hashCode . Zawierają wszystkie pola, podobnie jak klasa Kontakt .

2.3 Konstruktor kanoniczny

Konstruktor domyślny przyjmuje wszystkie pola jako parametry wejściowe i ustawia je jako pola. Na przykład domyślny konstruktor kanoniczny pokazano poniżej:
public Contact(String name, String email) {
    this.name = name;
    this.email = email;
}
Jeśli w klasie nagrywającej zdefiniujemy konstruktor o tej samej sygnaturze, zostanie on użyty zamiast konstruktora kanonicznego.

3. Praca z rekordami

Zachowanie wpisu możemy zmienić na kilka sposobów. Przyjrzyjmy się niektórym przypadkom użycia i sposobom ich osiągnięcia.

3.1. Zastępowanie domyślnych implementacji

Dowolną domyślną implementację można zmienić, zastępując ją. Przykładowo, jeżeli chcemy zmienić zachowanie metody toString , to możemy ją zastąpić pomiędzy nawiasami klamrowymi {} .
public record Contact(String name, String email) {
    @Override
    public String toString() {
        return "Contact[" +
                "name is '" + name + '\'' +
                ", email is" + email +
                ']';
    }
}
Podobnie możemy zastąpić metody równości i hashCode .

3.2. Kompaktowe zestawy konstrukcyjne

Czasami chcemy, aby konstruktorzy robili coś więcej niż tylko inicjalizację pól. Aby to zrobić, możemy dodać niezbędne operacje do naszego wpisu w konstruktorze kompaktowym. Nazywa się to kompaktowym, ponieważ nie wymaga definiowania inicjalizacji pola ani listy parametrów.
public record Contact(String name, String email) {
    public Contact {
        if(!email.contains("@")) {
            throw new IllegalArgumentException("Invalid email");
        }
    }
}
Należy pamiętać, że nie ma listy parametrów, a inicjalizacja nazwy i adresu e-mail następuje w tle przed wykonaniem sprawdzenia.

3.3. Dodawanie konstruktorów

Do rekordu można dodać wielu konstruktorów. Spójrzmy na kilka przykładów i ograniczeń. Najpierw dodajmy nowe prawidłowe konstruktory:
public record Contact(String name, String email) {
    public Contact(String email) {
        this("John Doe", email);
    }

    // replaces the default constructor
    public Contact(String name, String email) {
        this.name = name;
        this.email = email;
    }
}
W pierwszym przypadku dostęp do konstruktora domyślnego uzyskuje się za pomocą słowa kluczowego this . Drugi konstruktor zastępuje konstruktora domyślnego, ponieważ ma tę samą listę parametrów. W tym wypadku sam wpis nie utworzy konstruktora domyślnego. Konstruktorów obowiązuje kilka ograniczeń.

1. Konstruktor domyślny powinien być zawsze wywoływany z dowolnego innego konstruktora.

Na przykład poniższy kod nie zostanie skompilowany:
public record Contact(String name, String email) {
    public Contact(String name) {
        this.name = "John Doe";
        this.email = null;
    }
}
Ta reguła zapewnia, że ​​pola są zawsze inicjowane. Gwarantowane jest również, że operacje zdefiniowane w konstruktorze kompaktowym zostaną zawsze wykonane.

2. Nie jest możliwe przesłonięcie konstruktora domyślnego, jeśli zdefiniowany jest konstruktor kompaktowy.

Po zdefiniowaniu konstruktora kompaktowego automatycznie tworzony jest konstruktor domyślny z inicjalizacją i logiką konstruktora kompaktowego. W takim przypadku kompilator nie pozwoli nam na zdefiniowanie konstruktora z takimi samymi argumentami jak konstruktor domyślny. Na przykład w tym kodzie kompilacja nie nastąpi:
public record Contact(String name, String email) {
    public Contact {
        if(!email.contains("@")) {
            throw new IllegalArgumentException("Invalid email");
        }
    }
    public Contact(String name, String email) {
        this.name = name;
        this.email = email;
    }
}

3.4. Implementowanie interfejsów

Jak w przypadku każdej klasy, możemy zaimplementować interfejsy w rekordach.
public record Contact(String name, String email) implements Comparable<Contact> {
    @Override
    public int compareTo(Contact o) {
        return name.compareTo(o.name);
    }
}
Ważna uwaga. Aby zapewnić całkowitą niezmienność, rekordy nie mogą być dziedziczone. Zgłoszenia są ostateczne i nie można ich rozszerzać. Nie mogą także przedłużać innych zajęć.

3.5. Dodawanie metod

Oprócz konstruktorów, które przesłaniają metody i implementacje interfejsów, możemy także dodać dowolne metody. Na przykład:
public record Contact(String name, String email) {
    String printName() {
        return "My name is:" + this.name;
    }
}
Możemy także dodać metody statyczne. Na przykład, jeśli chcemy mieć metodę statyczną zwracającą wyrażenie regularne, według którego możemy sprawdzić pocztę, możemy ją zdefiniować w sposób pokazany poniżej:
public record Contact(String name, String email) {
    static Pattern emailRegex() {
        return Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$", Pattern.CASE_INSENSITIVE);
    }
}

3.6. Dodawanie pól

Nie możemy dodać pól instancji do rekordu. Możemy jednak dodać pola statyczne.
public record Contact(String name, String email) {
    private static final Pattern EMAIL_REGEX_PATTERN = Pattern
            .compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$", Pattern.CASE_INSENSITIVE);

    static Pattern emailRegex() {
        return EMAIL_REGEX_PATTERN;
    }
}
Należy pamiętać, że w polach statycznych nie ma żadnych ukrytych ograniczeń. W razie potrzeby mogą być one publicznie dostępne i nieostateczne.

Wniosek

Rekordy to świetny sposób na definiowanie klas danych. Są znacznie wygodniejsze i wydajniejsze niż podejście JavaBeans/POJO. Ponieważ są łatwe do wdrożenia, powinny być preferowane w stosunku do innych sposobów tworzenia obiektów wartości.
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION