CodeGym /Kursy /JAVA 25 SELF /Dopasowywanie wzorców w switch (Java 17/21+)

Dopasowywanie wzorców w switch (Java 17/21+)

JAVA 25 SELF
Poziom 65, Lekcja 2
Dostępny

1. Klasyczny switch: ograniczenia przeszłości

Przypomnijmy, jaki był switch w Javie przed pojawieniem się dopasowywania wzorców. Był, szczerze mówiąc, dość konserwatywny. Działał tylko na prymitywach (int, char, a nieco później enum, String), a jeśli chcieliście obsłużyć różne typy obiektów — trzeba było pisać długie łańcuchy if-else.

Object obj = ...;
if (obj instanceof String) {
    String s = (String) obj;
    System.out.println("Napis: " + s);
} else if (obj instanceof Integer) {
    Integer i = (Integer) obj;
    System.out.println("Liczba: " + i);
} else if (obj == null) {
    System.out.println("null!");
} else {
    System.out.println("Coś innego");
}

Wygodne? Nieszczególnie. Kod jest rozwlekły, łatwo o błąd, a jeśli dojdzie nowy typ — znów trzeba przepisywać łańcuch if-else.

2. Dopasowywanie wzorców w switch: rewolucja w Javie 17/21+

Java 17 (preview), a finalnie Java 21+, wprowadziła nową wersję switch, która wspiera dopasowywanie wzorców (pattern matching). Teraz switch może działać nie tylko z prymitywami, lecz także z dowolnymi obiektami i typami, nawet z null, oraz wspiera dodatkowe warunki (wyrażenia guard).

Nowa składnia: zwięzłość i moc

Przykład prostego dopasowywania wzorców w switch:

Object obj = ...;
switch (obj) {
    case String s -> System.out.println("Napis: " + s);
    case Integer i -> System.out.println("Liczba: " + i);
    case null -> System.out.println("null!");
    default -> System.out.println("Coś innego");
}

Co tu się dzieje?

  • Jeśli obj jest napisem, zmienna s automatycznie ma typ String.
  • Jeśli obj jest liczbą całkowitą, zmienna i staje się typem Integer.
  • Jeśli obj to null, zadziała specjalna gałąź.
  • Wszystko pozostałe — default.

Dlaczego to jest wygodne?

  • Bez ręcznego rzutowania typów: Java sama dba o bezpieczeństwo typów.
  • Mniej kodu: Bez zbędnych nawiasów, rzutowań i duplikacji.
  • Bezpieczniej: Kompilator sprawdza, czy nie zapomniano obsłużyć możliwych wariantów (zwłaszcza przy sealed-klasach, o których powiemy później).
  • Łatwo dodawać nowe typy: Po prostu dodaj kolejną gałąź case.

3. Dopasowywanie wzorców w switch: podstawowe przykłady

Przykład 1: Obsługa różnych typów

Object obj = 42;

switch (obj) {
    case String s -> System.out.println("Napis: " + s);
    case Integer i -> System.out.println("Liczba: " + i);
    case null -> System.out.println("To jest null");
    default -> System.out.println("Nieznany typ");
}

Wynik:

Liczba: 42

Przykład 2: Użycie wyrażeń guard (when)

Czasem samo poznanie typu to za mało — chcemy dodać dodatkowy warunek.

Object obj = "Hello, world!";

switch (obj) {
    case String s when s.length() > 10 -> System.out.println("Długi napis: " + s);
    case String s -> System.out.println("Krótki napis: " + s);
    case Integer i when i > 0 -> System.out.println("Liczba dodatnia: " + i);
    case Integer i -> System.out.println("Liczba niedodatnia: " + i);
    default -> System.out.println("Coś innego");
}

Wynik:

Długi napis: Hello, world!

Zwróć uwagę:

  • when to wyrażenie guard, dodatkowa weryfikacja dla case.
  • Jeśli warunek nie jest spełniony, sprawdzane są kolejne case.

Przykład 3: Obsługa null

Object obj = null;

switch (obj) {
    case null -> System.out.println("To jest null!");
    default -> System.out.println("Nie null");
}

Wynik:

To jest null!

4. Dopasowywanie wzorców w switch z klasami i dziedziczeniem

Dopasowywanie wzorców w switch szczególnie dobrze działa z hierarchiami klas.

sealed interface Shape permits Circle, Rectangle, Square {}

final class Circle implements Shape {
    final double radius;
    Circle(double radius) { this.radius = radius; }
}

final class Rectangle implements Shape {
    final double width, height;
    Rectangle(double width, double height) { this.width = width; this.height = height; }
}

final class Square implements Shape {
    final double side;
    Square(double side) { this.side = side; }
}
Shape shape = new Rectangle(2, 3);

String description = switch (shape) {
    case Circle c -> "Okrąg o promieniu " + c.radius;
    case Rectangle r -> "Prostokąt " + r.width + "x" + r.height;
    case Square s -> "Kwadrat o boku " + s.side;
};
System.out.println(description);

Wynik:

Prostokąt 2.0x3.0

Ważne: Jeśli używasz sealed-klas i obsłużysz wszystkie ich warianty, gałąź default nie jest wymagana — kompilator rozumie, że innych wariantów być nie może!

5. Użycie switch z klasami record i wzorcami zagnieżdżonymi

Dopasowywanie wzorców w switch wspiera pracę z klasami record i wzorcami zagnieżdżonymi (począwszy od Java 21+).

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

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

String result = switch (obj) {
    case Line(Point(int x1, int y1), Point(int x2, int y2)) ->
        "Linia od (" + x1 + "," + y1 + ") do (" + x2 + "," + y2 + ")";
    case Point(int x, int y) -> "Punkt (" + x + "," + y + ")";
    default -> "Nieznany obiekt";
};
System.out.println(result);

Wynik:

Linia od (1,2) do (3,4)

6. Specyfika i ograniczenia dopasowywania wzorców w switch

Zmienne są dostępne tylko wewnątrz case

Zmienna zadeklarowana w wzorcu (na przykład String s) istnieje tylko wewnątrz odpowiadającego jej bloku case. Poza nim jest niedostępna.

switch (obj) {
    case String s -> System.out.println(s); // tutaj s jest widoczne
    // System.out.println(s); // tu błąd kompilacji!
}

Obsługa null

  • W starym switch na obiektach próba przekazania null prowadziła do NullPointerException.
  • W nowym dopasowywaniu wzorców switch można jawnie obsłużyć null przez case null.

Wyrażenia guard (when)

Dodatkowe warunki (when) są dostępne tylko w switch w stylu wyrażenia (składnia strzałkowa ->).

case String s when s.isEmpty() -> System.out.println("Pusty napis");

Wymagania dla JDK i IDE

  • Dopasowywanie wzorców w switch to funkcja Java 17 (preview) i Java 21+ (stabilnie).
  • Do działania potrzebny jest JDK 21 i IDE wspierające nowe możliwości (na przykład IntelliJ IDEA 2023+).

7. Zastosowanie dopasowywania wzorców w switch w rzeczywistej aplikacji

Przyjrzyjmy się aplikacji edukacyjnej — prostemu trackerowi zadań. Załóżmy, że masz abstrakcyjny typ zadania i różne jego rodzaje:

sealed interface Task permits SimpleTask, DeadlineTask, MeetingTask {}

final class SimpleTask implements Task {
    final String description;
    SimpleTask(String description) { this.description = description; }
}

final class DeadlineTask implements Task {
    final String description;
    final java.time.LocalDate deadline;
    DeadlineTask(String description, java.time.LocalDate deadline) {
        this.description = description;
        this.deadline = deadline;
    }
}

final class MeetingTask implements Task {
    final String topic;
    final java.time.LocalTime time;
    MeetingTask(String topic, java.time.LocalTime time) {
        this.topic = topic;
        this.time = time;
    }
}

Cała logika w jednym, zwięzłym switch:

Task task = new DeadlineTask("Oddać projekt", java.time.LocalDate.of(2024, 7, 1));

String info = switch (task) {
    case SimpleTask st -> "Zwykłe zadanie: " + st.description;
    case DeadlineTask dt -> "Zadanie z terminem: " + dt.description + ", do " + dt.deadline;
    case MeetingTask mt -> "Spotkanie: " + mt.topic + " o " + mt.time;
};
System.out.println(info);

Wynik:

Zadanie z terminem: Oddać projekt, do 2024-07-01

8. Typowe błędy przy używaniu dopasowywania wzorców w switch

Błąd nr 1: zapomniano obsłużyć null. Jeśli nie dodasz case null, a obj może być null — otrzymasz MatchException (lub NullPointerException w starych switch). Zawsze jawnie obsługuj null, jeśli to możliwe.

Błąd nr 2: nie uwzględniono wszystkich potomków klasy sealed. Jeśli używasz sealed-klasy i nie obsłużysz wszystkich wariantów w switch, kompilator zgłosi błąd lub wymusi gałąź default. Nie zapominaj dodawać nowych case przy rozszerzaniu hierarchii.

Błąd nr 3: zmienna wzorca jest niedostępna poza blokiem case. Zmienna zadeklarowana w wzorcu istnieje tylko wewnątrz bloku strzałkowego. Próba użycia jej poza tym blokiem spowoduje błąd kompilacji.

Błąd nr 4: używanie nowych funkcji na starej JDK lub w starej IDE. Dopasowywanie wzorców w switch wymaga Java 21+ i odpowiedniego wsparcia w IDE. Jeśli użyjesz starej wersji — pojawi się błąd kompilacji lub niepoprawne podpowiedzi.

Błąd nr 5: zamieszanie z wyrażeniami guard. Próbujesz używać when w starym blokowym switch? Nie zadziała. Guardy są dostępne tylko dla składni strzałkowej ->.

Błąd nr 6: zapomniano o gałęzi default przy pracy z otwartymi hierarchiami. Jeśli Twój switch działa nie tylko z sealed-klasami, koniecznie dodaj default — inaczej nie wszystkie warianty zostaną obsłużone.

1
Zadanie
JAVA 25 SELF, poziom 65, lekcja 2
Niedostępne
Klasyfikator liczb dla systemu analitycznego 🔢
Klasyfikator liczb dla systemu analitycznego 🔢
1
Zadanie
JAVA 25 SELF, poziom 65, lekcja 2
Niedostępne
Inteligentny system identyfikacji zwierząt dla kliniki weterynaryjnej 🐕🐈🦜
Inteligentny system identyfikacji zwierząt dla kliniki weterynaryjnej 🐕🐈🦜
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION