Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Шаблоны в 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 failed

String — разновидность CharSequence, поэтому первая ветка забирает все строки. Ставьте частные шаблоны выше общих.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.