Шаблоны в switch
С Java 21 в switch можно проверять не только конкретные значения, но и типы. Каждая ветка case содержит шаблон, как в instanceof, и выполняется первая подходящая. Длинные цепочки if-else с instanceof превращаются в короткий и наглядный switch.
Шаблоны типов в case
Перепишем пример из прошлого урока. switch возвращает значение, поэтому его можно сразу отдать в return:
String describe(Object value) {
return switch (value) {
case Integer number -> "целое число " + number;
case String text -> "строка «" + text + "»";
default -> "что-то другое";
};
}
void main() {
IO.println(describe(42));
IO.println(describe("Анна"));
IO.println(describe(2.5));
}Результат:
целое число 42
строка «Анна»
что-то другоеВетка default обязательна: тип Object может оказаться чем угодно, и switch должен знать, что делать в любом случае.
Условия when
К шаблону можно добавить условие словом when. Ветка выполнится, только если подошёл и тип, и условие:
String classify(Object value) {
return switch (value) {
case Integer n when n < 0 -> "отрицательное число";
case Integer n when n == 0 -> "ноль";
case Integer n -> "положительное число";
case String s when s.isEmpty() -> "пустая строка";
case String s -> "строка";
default -> "неизвестно";
};
}
void main() {
IO.println(classify(-7));
IO.println(classify(0));
IO.println(classify(15));
IO.println(classify(""));
}Результат:
отрицательное число
ноль
положительное число
пустая строкаВетки проверяются сверху вниз, поэтому частные случаи с when пишут раньше общих.
case null
Обычный switch на null выбрасывает NullPointerException. Чтобы обработать null явно, добавьте ветку case null:
String check(Object value) {
return switch (value) {
case null -> "значения нет";
case String s -> "строка " + s;
default -> "другое";
};
}
void main() {
IO.println(check(null));
IO.println(check("ок"));
}Результат:
значения нет
строка окЗапечатанные иерархии
Если switch перебирает подтипы запечатанного (sealed) интерфейса, default не нужен: компилятор знает весь список наследников и сам проверяет, что ни один не забыт. Это называется полнотой (exhaustiveness):
sealed interface Shape permits Circle, Square, Rectangle {}
record Circle(double radius) implements Shape {}
record Square(double side) implements Shape {}
record Rectangle(double width, double height) implements Shape {}
double area(Shape shape) {
return switch (shape) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Square s -> s.side() * s.side();
case Rectangle r -> r.width() * r.height();
};
}
void main() {
IO.println(area(new Square(3)));
IO.println(area(new Rectangle(2, 5)));
IO.println(Math.round(area(new Circle(1)) * 100) / 100.0);
}Результат:
9.0
10.0
3.14Если позже добавить новую фигуру, все такие switch перестанут компилироваться, пока в них не появится новая ветка. Ошибка находится до запуска программы.
Полнота для перечислений
Проверка полноты работает и для перечислений (enum): если switch перебирает все значения, default не нужен. Если забыть одно, компилятор сообщит об ошибке. Здесь перебраны все три:
enum Level { LOW, MEDIUM, HIGH }
String color(Level level) {
return switch (level) {
case LOW -> "зелёный";
case MEDIUM -> "жёлтый";
case HIGH -> "красный";
};
}
void main() {
IO.println(color(Level.MEDIUM));
}Результат:
жёлтыйЧастая ошибка
Если над частным шаблоном стоит более общий, частный никогда не сработает. Компилятор называет это доминированием и выдаёт ошибку:
String describe(Object value) {
return switch (value) {
case CharSequence cs -> "текст";
case String s -> "строка";
default -> "другое";
};
}
void main() {
IO.println(describe("Олег"));
}Результат:
Main.java:4: error: this case label is dominated by a preceding case label
case String s -> "строка";
^
1 error
error: compilation failedString — разновидность CharSequence, поэтому первая ветка забирает все строки. Ставьте частные шаблоны выше общих.