JavaRush /Курси /JAVA 25 SELF /Pattern Matching у switch (Java 17/21+)

Pattern Matching у switch (Java 17/21+)

JAVA 25 SELF
Рівень 65 , Лекція 2
Відкрита

1. Класичний switch: обмеження минулого

Згадаємо, яким був switch у Java до появи pattern matching. Він був, скажімо чесно, доволі консервативним. Працював лише з примітивами (int, char, а трохи пізніше enum, String), а якщо ви хотіли обробити різні типи об’єктів — доводилося писати довгі if-else.

Object obj = ...;
if (obj instanceof String) {
    String s = (String) obj;
    System.out.println("Рядок: " + s);
} else if (obj instanceof Integer) {
    Integer i = (Integer) obj;
    System.out.println("Число: " + i);
} else if (obj == null) {
    System.out.println("null!");
} else {
    System.out.println("Щось інше");
}

Зручно? Не дуже. Код громіздкий, легко помилитися, а якщо додасться новий тип — знову доведеться переписувати ланцюжок if-else.

2. Pattern Matching у switch: революція в Java 17/21+

У Java 17 (preview), а остаточно — у Java 21+, було представлено нову версію switch, яка підтримує pattern matching. Тепер switch може працювати не лише з примітивами, а й з будь-якими об’єктами, типами, навіть з null, і підтримує додаткові умови (guard-вирази).

Новий синтаксис: лаконічність і потужність

Приклад простого pattern matching у switch:

Object obj = ...;
switch (obj) {
    case String s -> System.out.println("Рядок: " + s);
    case Integer i -> System.out.println("Число: " + i);
    case null -> System.out.println("null!");
    default -> System.out.println("Щось інше");
}

Що тут відбувається?

  • Якщо obj — рядок, виконується автоматичне перетворення типу до String, і з’являється змінна s.
  • Якщо obj — ціле число, змінна i має тип Integer.
  • Якщо objnull, спрацює спеціальна гілка.
  • Усе інше — default.

Чому це зручно?

  • Немає ручного перетворення типів: Java сама дбає про безпеку типів.
  • Менше коду: ніяких зайвих дужок, перетворень типів і дублювання.
  • Безпечніше: компілятор перевіряє, що ви не забули обробити можливі варіанти (особливо з sealed-класами, про які поговоримо далі).
  • Легко додавати нові типи: просто додайте ще одну гілку case.

3. Pattern Matching у switch: базові приклади

Приклад 1: Обробка різних типів

Object obj = 42;

switch (obj) {
    case String s -> System.out.println("Рядок: " + s);
    case Integer i -> System.out.println("Число: " + i);
    case null -> System.out.println("Це null");
    default -> System.out.println("Невідомий тип");
}

Виведення:

Число: 42

Приклад 2: Використання guard-виразів (when)

Іноді замало просто дізнатися тип — хочеться додати додаткову умову.

Object obj = "Hello, world!";

switch (obj) {
    case String s when s.length() > 10 -> System.out.println("Довгий рядок: " + s);
    case String s -> System.out.println("Короткий рядок: " + s);
    case Integer i when i > 0 -> System.out.println("Додатне число: " + i);
    case Integer i -> System.out.println("Недодатне число: " + i);
    default -> System.out.println("Щось інше");
}

Виведення:

Довгий рядок: Hello, world!

Зверніть увагу:

  • when — це guard-вираз, додаткова перевірка для case.
  • Якщо умова не виконується, перевіряються наступні case.

Приклад 3: Обробка null

Object obj = null;

switch (obj) {
    case null -> System.out.println("Це null!");
    default -> System.out.println("Не null");
}

Виведення:

Це null!

4. Pattern Matching у switch із класами та наслідуванням

Pattern matching у switch особливо добре працює з ієрархіями класів.

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 -> "Коло з радіусом " + c.radius;
    case Rectangle r -> "Прямокутник " + r.width + "x" + r.height;
    case Square s -> "Квадрат зі стороною " + s.side;
};
System.out.println(description);

Виведення:

Прямокутник 2.0x3.0

Особливість: Якщо ви використовуєте sealed-класи й обробили всі їх варіанти, гілка default не потрібна — компілятор розуміє, що інших варіантів бути не може!

5. Використання switch із record-класами та вкладеними патернами

Pattern matching у switch підтримує роботу з record-класами та вкладеними патернами (починаючи з 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)) ->
        "Лінія від (" + x1 + "," + y1 + ") до (" + x2 + "," + y2 + ")";
    case Point(int x, int y) -> "Точка (" + x + "," + y + ")";
    default -> "Невідомий об’єкт";
};
System.out.println(result);

Виведення:

Лінія від (1,2) до (3,4)

6. Особливості та обмеження pattern matching у switch

Змінні доступні лише всередині case

Змінна, оголошена в патерні (наприклад, String s), існує лише у відповідному блоці case. За його межами вона недоступна.

switch (obj) {
    case String s -> System.out.println(s); // тут s доступна
    // System.out.println(s); // тут помилка компіляції!
}

Обробка null

  • У старому switch на об’єктах спроба передати null призводила до NullPointerException.
  • У новому pattern matching switch можна явно обробити null через case null.

Guard-вирази (when)

Додаткові умови (when) доступні лише в switch у стилі expression (стрілочний синтаксис ->).

case String s when s.isEmpty() -> System.out.println("Порожній рядок");

Вимоги до JDK та IDE

  • Pattern matching у switch — це функціональність, доступна в Java 17 (preview) і стабільна в Java 21+.
  • Для роботи потрібен JDK 21 та IDE, що підтримує нові можливості (наприклад, IntelliJ IDEA 2023+).

7. Застосування switch із pattern matching у реальному застосунку

Розглянемо навчальний застосунок — простий таск-трекер. Припустімо, у вас є абстрактний тип завдання й різні його види:

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;
    }
}

Уся логіка в одному компактному switch:

Task task = new DeadlineTask("Здати проєкт", java.time.LocalDate.of(2024, 7, 1));

String info = switch (task) {
    case SimpleTask st -> "Звичайне завдання: " + st.description;
    case DeadlineTask dt -> "Завдання з дедлайном: " + dt.description + ", до " + dt.deadline;
    case MeetingTask mt -> "Зустріч: " + mt.topic + " о " + mt.time;
};
System.out.println(info);

Виведення:

Завдання з дедлайном: Здати проєкт, до 2024-07-01

8. Типові помилки при використанні pattern matching у switch

Помилка № 1: забули обробити null. Якщо ви не додали case null, а obj може бути null — отримаєте MatchException (або NullPointerException у старих switch). Завжди явно обробляйте null, якщо це можливо.

Помилка № 2: не врахували всіх нащадків sealed-класу. Якщо ви використовуєте sealed-клас і не обробили всі варіанти в switch, компілятор видасть помилку або вимагатиме гілку default. Не забувайте додавати нові case під час розширення ієрархії.

Помилка № 3: змінна патерна недоступна поза блоком case. Змінна, оголошена в патерні, існує лише всередині стрілочного блоку. Спроба використати її поза цим блоком спричинить помилку компіляції.

Помилка № 4: використання нових можливостей на старій JDK або в старій IDE. Pattern matching у switch вимагає Java 21+ і відповідної підтримки у вашій IDE. Якщо використовуєте стару версію — отримаєте помилку компіляції або некоректні підказки.

Помилка № 5: плутанина з guard-виразами. Намагаєтеся використовувати when у старому блочному switch? Не вийде. Гарди доступні лише для стрілочного синтаксису ->.

Помилка № 6: забули гілку default під час роботи з відкритими ієрархіями. Якщо ваш switch працює не лише з sealed-класами, обов’язково додавайте default — інакше не всі варіанти буде оброблено.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ