CodeGym /Kursy /JAVA 25 SELF /Record Patterns (Java 21+): składnia, przykłady

Record Patterns (Java 21+): składnia, przykłady

JAVA 25 SELF
Poziom 65, Lekcja 3
Dostępny

1. Zagłębiamy się w klasy record

Zanim zanurzymy się w record patterns, odświeżmy sobie, czym jest klasa record.

Record — to zwięzła, niemutowalna klasy z automatycznie generowanymi metodami equals, hashCode, toString, a także z automatycznym tworzeniem getterów dla wszystkich komponentów. Klasy record pojawiły się w Javie 16 i były prawdziwym wybawieniem dla wszystkich, którzy mieli dość ręcznego pisania „DTO-ki”.

// Klasyka: Point z dwoma polami
record Point(int x, int y) {}

Tworzenie obiektu i dostęp do pól:

Point p = new Point(10, 20);
System.out.println(p.x()); // 10
System.out.println(p.y()); // 20
System.out.println(p);     // Point[x=10, y=20]

Po co nam Record Patterns

Przed Javą 21, jeśli chcieliście sprawdzić, że obiekt — to pewien record, i pobrać jego pola, trzeba było pisać sporo ręcznego kodu: sprawdzenie typu przez instanceof, rzutowanie i wywołanie getterów.

Object obj = new Point(5, 7);
if (obj instanceof Point) {
    Point p = (Point) obj;
    int x = p.x();
    int y = p.y();
    System.out.println("x=" + x + ", y=" + y);
}

Jeśli record jest złożony lub zagnieżdżony, kod szybko zamienia się w „las rozpakowań”. A używanie takiego rozpoznawania w switch było wcześniej niewygodne.

Record patterns pozwalają rozpakowywać wartości klas record bezpośrednio w dopasowaniu wzorców: zarówno w instanceof, jak i w switch. To sprawia, że kod jest krótszy, bezpieczniejszy i bardziej zrozumiały.

2. Składnia record patterns: najprostszy przykład

record Point(int x, int y) {}

Object obj = new Point(10, 20);

if (obj instanceof Point(int x, int y)) {
    System.out.println("x=" + x + ", y=" + y);
}

Co tu się dzieje?

  • obj instanceof Point(int x, int y) — sprawdzamy typ i od razu rozpakowujemy komponenty do zmiennych x i y.
  • Zmienne x i y są dostępne tylko wewnątrz bloku if.

Wynik:

x=10, y=20

Uwaga: jeśli obj nie jest Point lub jest null, blok if nie zostanie wykonany, a zmienne nie zostaną zadeklarowane.

3. Record patterns w switch: zwięzłość i bezpieczeństwo

record Point(int x, int y) {}
record Circle(Point center, int radius) {}

Object shape = new Point(3, 4);

switch (shape) {
    case Point(int x, int y) -> System.out.println("Point: x=" + x + ", y=" + y);
    case Circle(Point center, int r) -> System.out.println("Circle with radius " + r);
    default -> System.out.println("Unknown shape!");
}
  • W case Point(int x, int y) od razu mamy dostęp do x i y.
  • W case Circle(Point center, int r) dostępne są center i r.

Wynik:

Point: x=3, y=4

Z sealed-hierarchiami switch staje się wyczerpujący: jeśli zapomnisz obsłużyć wariant, kompilator ostrzeże.

4. Zagnieżdżone record patterns: rozpakowywanie wewnątrz rozpakowywania

record Point(int x, int y) {}
record Line(Point start, Point end) {}

Object obj = new Line(new Point(1, 2), new Point(3, 4));

if (obj instanceof Line(Point(int x1, int y1), Point(int x2, int y2))) {
    System.out.println("Line from (" + x1 + "," + y1 + ") to (" + x2 + "," + y2 + ")");
}

Wynik:

Line from (1,2) to (3,4)

Pracujesz z drzewami, grafami, złożonymi strukturami? Zagnieżdżone wzorce to twoi sprzymierzeńcy.

5. Record patterns z wyrażeniami guard (when)

Object obj = new Point(0, 100);

if (obj instanceof Point(int x, int y) && x == 0) {
    System.out.println("Punkt leży na osi Y: y=" + y);
}

W switch robi się to za pomocą when:

switch (obj) {
    case Point(int x, int y) when x == 0 -> System.out.println("Na osi Y: y=" + y);
    case Point(int x, int y) when y == 0 -> System.out.println("Na osi X: x=" + x);
    case Point(int x, int y) -> System.out.println("Zwykły punkt");
    default -> System.out.println("To nie punkt");
}

6. Zastosowanie record patterns w realnej aplikacji

Weźmy praktyczny przykład — figury geometryczne.

record Point(int x, int y) {}
record Circle(Point center, int radius) {}
record Rectangle(Point topLeft, int width, int height) {}
public static void printShapeInfo(Object shape) {
    switch (shape) {
        case Point(int x, int y) -> System.out.println("Point: (" + x + ", " + y + ")");
        case Circle(Point(int x, int y), int radius) ->
            System.out.println("Circle: center=(" + x + ", " + y + "), radius=" + radius);
        case Rectangle(Point(int x, int y), int width, int height) ->
            System.out.println("Rectangle: topLeft=(" + x + ", " + y + "), size=" + width + "x" + height);
        default -> System.out.println("Unknown shape");
    }
}

Przykład wywołania:

printShapeInfo(new Rectangle(new Point(5, 10), 20, 30));

Wynik:

Rectangle: topLeft=(5, 10), size=20x30

7. Ograniczenia i niuanse użycia record patterns

Tylko dla klas record. Record patterns działają tylko z obiektami, które rzeczywiście są record-klasami.

class NotARecord {
    int a, b;
    NotARecord(int a, int b) { this.a = a; this.b = b; }
}

// Błąd! To nie jest record
// if (obj instanceof NotARecord(int a, int b)) { ... }

Zgodność komponentów co do liczby i typu. Wzorzec musi odpowiadać komponentom klasy record.

record Pair(int a, String b) {}

// Błąd: typy się nie zgadzają
// if (obj instanceof Pair(String a, int b)) { ... }

Zmienne dostępne tylko wewnątrz bloku. Nazwy zadeklarowane w pattern są widoczne w ciele konkretnego if lub case, gdzie dopasowanie zakończyło się powodzeniem.

8. Zagnieżdżone wzorce: przykład z drzewem

sealed interface Expr permits NumberExpr, PlusExpr, MinusExpr {}

record NumberExpr(int value) implements Expr {}
record PlusExpr(Expr left, Expr right) implements Expr {}
record MinusExpr(Expr left, Expr right) implements Expr {}
int eval(Expr expr) {
    return switch (expr) {
        case NumberExpr(int value) -> value;
        case PlusExpr(Expr left, Expr right) -> eval(left) + eval(right);
        case MinusExpr(Expr left, Expr right) -> eval(left) - eval(right);
    };
}

Przykład użycia:

Expr e = new PlusExpr(new NumberExpr(7), new MinusExpr(new NumberExpr(10), new NumberExpr(3)));
System.out.println(eval(e)); // 7 + (10 - 3) = 14

Tutaj od razu rozpakowujemy NumberExpr(int value), PlusExpr(Expr left, Expr right) i MinusExpr(Expr left, Expr right) — kod staje się zwięzły i wyrazisty.

9. Tabela: porównanie dopasowywania wzorców z i bez record patterns

Sposób Ręczne rzutowanie typu Dopasowywanie wzorców z record patterns
Sprawdzenie typu
if (obj instanceof ...)
if (obj instanceof Point(int x, int y))
Rzutowanie typu
(Point) obj
Nie trzeba
Dostęp do pól
p.x(), p.y()
x, y
Zagnieżdżone rozpakowywanie Dużo ręcznego kodu Zagnieżdżone wzorce
Użycie w switch Niewygodne/niemożliwe Łatwo i zwięźle

10. Typowe błędy przy użyciu record patterns

Błąd nr 1: próba zastosowania record pattern do klasy, która nie jest recordem. Jeśli napisaliście zwykłą klasę, a potem próbujecie „rozpakować” ją jak record, kompilator na to nie pozwoli. Record patterns działają tylko z prawdziwymi record-klasami.

Błąd nr 2: niezgodna liczba lub typ komponentów. Wzorzec musi w pełni odpowiadać sygnaturze komponentów record. Na przykład dla record Pair(int a, String b) nie można pisać Pair(int x, int y).

Błąd nr 3: próba użycia zmiennych wzorca poza blokiem. Nazwy zadeklarowane w wzorcu są dostępne tylko wewnątrz odpowiedniego if/case. Poza blokiem takie zmienne nie istnieją.

Błąd nr 4: używanie record patterns na starych wersjach JDK. Wsparcie pojawiło się w Javie 21. Na Javie 17 i starszych dostaniesz błąd składni. Sprawdź wersję JDK i wsparcie w IDE.

Błąd nr 5: zbyt głęboko zagnieżdżone wzorce. Możliwości zagnieżdżania są potężne, ale nie komplikuj bez potrzeby: zbyt głęboka struktura pogarsza czytelność i zwiększa ryzyko błędów.

1
Zadanie
JAVA 25 SELF, poziom 65, lekcja 3
Niedostępne
Edytor geometryczny: opis położenia punktów 📍
Edytor geometryczny: opis położenia punktów 📍
1
Zadanie
JAVA 25 SELF, poziom 65, lekcja 3
Niedostępne
Mózg kalkulatora: obliczanie złożonych wyrażeń 🧠➕➖
Mózg kalkulatora: obliczanie złożonych wyrażeń 🧠➕➖
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION