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.
- Якщо obj — null, спрацює спеціальна гілка.
- Усе інше — 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 — інакше не всі варіанти буде оброблено.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ