Современные выражения switch в Java
Используйте современные выражения switch в Java: стрелочные метки, yield, полнота покрытия и сопоставление с образцом.
Традиционный оператор switch существует в Java с версии 1.0, но нёс немало проблем: ошибки из-за проваливания, повторяющиеся операторы break и отсутствие возможности возвращать значение. Выражения switch, финализированные в Java 14, устраняют все эти недостатки. Они превращают switch из неуклюжего оператора управления потоком в лаконичное выражение, возвращающее значение.
В этой главе рассматривается современный switch: стрелочные метки, ключевое слово yield, многозначные метки case, проверка полноты покрытия и принципиальные отличия новой формы от привычного оператора.
От оператора к выражению
Классический оператор switch выполняет побочные эффекты и требует break для предотвращения проваливания. Забудете break — выполнение молча перейдёт к следующему case, что является известным источником ошибок.
// Traditional switch statement (error-prone)
String kind;
switch (day) {
case SATURDAY:
case SUNDAY:
kind = "weekend";
break; // forget this and you fall through
default:
kind = "weekday";
}Выражение switch сводит всё это к одному присваиванию. Форма со стрелкой (->) никогда не проваливается, поэтому break не нужен.
// Modern switch expression
String kind = switch (day) {
case SATURDAY, SUNDAY -> "weekend";
default -> "weekday";
};Весь switch теперь вычисляется как значение, которое можно присвоить, вернуть или передать в качестве аргумента напрямую.
Стрелочные метки и многозначные case
Стрелочная метка case L -> связывает одну метку (или несколько, разделённых запятой) с единственным действием. Выполняется только подходящая ветка — проваливания не происходит.
int numLetters = switch (month) {
case JANUARY, JUNE, JULY -> 4;
case FEBRUARY, MARCH, APRIL, MAY -> 5;
case SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER -> switchOnLength(month);
case AUGUST -> 6;
};Группировка меток через запятую заменяет старый приём с несколькими пустыми строками case для общего тела и читается значительно понятнее.
| Возможность | Традиционный switch (case L:) | Современный switch (case L ->) |
|---|---|---|
| Проваливание | Да, если не поставить break | Нет, каждая ветка изолирована |
| Возврат значения | Нет | Да (это выражение) |
| Несколько меток | Несколько пустых строк case | Через запятую в одной строке |
| Область видимости переменных | Общая для всего блока | Локальная для каждого блока ветки |
Блоки и ключевое слово yield
Если ветка требует больше одного выражения, используйте блок { ... } и возвращайте значение через yield. Ключевое слово yield для выражения switch — то же, что return для метода: оно задаёт значение, которое производит ветка.
int gradePoints = switch (grade) {
case 'A' -> 4;
case 'B' -> 3;
default -> {
log("Unknown grade: " + grade);
yield 0; // the value this branch evaluates to
}
};Можно по-прежнему использовать двоеточные метки с yield, если предпочитаете старый синтаксис, но форма со стрелкой — идиоматичный современный выбор, полностью исключающий случайное проваливание.
Полнота покрытия и default
Выражение switch должно быть исчерпывающим: каждый возможный ввод должен быть обработан, так как выражение обязано возвращать значение в любом случае. Для большинства типов это обеспечивается веткой default. Для enum компилятор может проверить полноту покрытия напрямую — если вы охватываете все константы, default становится необязательным.
// No default needed: all enum constants are covered,
// so the compiler knows the switch is exhaustive.
boolean isWeekend = switch (day) {
case SATURDAY, SUNDAY -> true;
case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> false;
};Если вы пропустите константу и нет default, код не скомпилируется. Эта защита на этапе компиляции — одно из главных практических преимуществ перед старым оператором, который молча оставлял переменную неинициализированной.
Сопоставление с образцом в switch
В последних версиях Java возможности switch расширены: каждый case может сопоставляться с типом значения, а не только с константой. Это сопоставление с образцом для switch (превью в Java 17–20, финализировано в Java 21). Вместо цепочки проверок instanceof и приведений типов вы помечаете каждую ветку шаблоном типа и привязываете переменную в том же шаге.
static String describe(Object obj) {
return switch (obj) {
case Integer i -> "int " + i; // matches and binds i
case String s -> "string of length " + s.length();
case null -> "nothing"; // null can be its own label
default -> "something else";
};
}Сопоставление с образцом особенно мощно при работе с record и запечатанными типами: когда switch охватывает все разрешённые подтипы запечатанного типа, компилятор считает его исчерпывающим и default можно полностью опустить.
sealed interface Shape permits Circle, Square {}
record Circle(double radius) implements Shape {}
record Square(double side) implements Shape {}
static double area(Shape shape) {
return switch (shape) { // no default: all permitted types covered
case Circle c -> Math.PI * c.radius() * c.radius();
case Square s -> s.side() * s.side();
};
}switch мог проверять только enum, целочисленный тип или String. Шаблоны типов позволяют switch работать с любым ссылочным типом, что делает его естественной заменой длинных цепочек if/else if.Полный рабочий пример
Программа ниже объединяет все элементы: стрелочные метки с многозначными case, блок yield для вычисляемой ветки, исчерпывающее покрытие enum и выражение switch, присвоенное напрямую переменной.
Что следует вынести из запуска:
kind()возвращает результат выражения switch напрямую —MONDAYиFRIDAYвыводятweekday,SATURDAYвыводитweekend, и всё это без единогоbreak.- Две стрелочные ветки в
kind()охватывают все семь констант enum, поэтому switch исчерпывающий и не требуетdefault. - В
letterGrade()оценки 95, 83, 71 и 64 аккуратно отображаются через стрелочные метки в 4, 3, 2 и 1 балл соответственно. - Оценка 42 попадает в блок
default, который сначала выводит(failing score 42)через строку с побочным эффектом, а затем возвращает 0 черезyield— демонстрируя, как блочная ветка может выполнять работу перед возвратом значения. - Финальный
switchприсваиваетTWOпрямо в переменнуюlabel, доказывая, что выражение switch — это значение, которое можно хранить, а не просто управление потоком.
Когда использовать какую форму
- Используйте выражение switch (
case L ->), когда цель — вычислить и вернуть единственное значение. Оно исчерпывающее, без проваливания и читается как одно присваивание. - Традиционный оператор switch по-прежнему уместен, когда каждая ветка является чисто побочным эффектом (логирование, диспетчеризация) и нет значения для возврата.
- Используйте шаблоны типов, когда вы ветвитесь по типу объекта во время выполнения, особенно по константам
enumили подтипам запечатанного типа.
Для полной истории и формы с двоеточием смотрите выражения switch в Java.