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 — строка, переменная s автоматически приводится к типу String.
- Если 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) доступны только в expression-style switch (стрелочный синтаксис ->).
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. Применение pattern matching switch в реальном приложении
Рассмотрим учебное приложение — простой таск-трекер. Допустим, у вас есть абстрактный тип задачи и разные её виды:
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: переменная pattern недоступна вне блока case. Переменная, объявленная в паттерне, существует только внутри стрелочного блока. Попытка использовать её вне этого блока вызовет ошибку компиляции.
Ошибка №4: использование новых фич на старой JDK или в старой IDE. Pattern matching в switch требует Java 21+ и соответствующей поддержки в вашей IDE. Если используете старую версию — получите ошибку компиляции или некорректные подсказки.
Ошибка №5: путаница с guard-выражениями. Пытаетесь использовать when в старом блочном switch? Не выйдет. Гарды доступны только для стрелочного синтаксиса ->.
Ошибка №6: забыли default-ветку при работе с открытыми иерархиями. Если ваш switch работает не только с sealed-классами, обязательно добавляйте default — иначе не все варианты будут обработаны.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ