W3docs

Сопоставление с образцом в Java

Сопоставление с образцом в Java: instanceof, switch, образцы типов, образцы записей и деструктуризация.

Долгие годы код на Java, работающий со значениями неизвестного типа, следовал утомительному ритуалу: проверить тип с помощью instanceof, затем привести к этому типу, затем использовать. Сопоставление с образцом сворачивает этот ритуал в одно выражение. Образец описывает форму данных; если значение совпадает, Java привязывает его части к переменным, которые можно использовать сразу — без ручного приведения типов.

Сопоставление с образцом появлялось поэтапно: сначала образцы для instanceof, затем образцы в switch, затем образцы записей, деструктурирующие записи на составные части. Вместе они позволяют писать декларативный, типобезопасный код, который читается как данные, с которыми он работает.

В этой главе рассматриваются образец для instanceof, образцы типов в switch, охраняемые образцы и обработка null, а также образцы записей — и всё это объединяется в работающей программе. Глава опирается на три функции, которые вы, возможно, захотите изучить сначала: оператор instanceof, записи и выражения switch.

Сопоставление с образцом для instanceof

Классический шаблон «проверка и приведение» требовал трёх ссылок на один и тот же тип. Образец instanceof привязывает переменную одновременно с проверкой, и привязка действует везде, где проверка заведомо истинна.

Object value = "hello";

// Old way: test, then cast
if (value instanceof String) {
    String s = (String) value;
    System.out.println(s.length());
}

// Pattern way: test and bind together
if (value instanceof String s) {
    System.out.println(s.length());
}

Поскольку переменная привязки участвует в булевом выражении, можно продолжать сужение типа в том же if. Компилятор доказывает, что s безопасно использовать:

if (value instanceof String s && s.length() > 3) {
    System.out.println(s.toUpperCase());
}

Образцы в switch

switch может сопоставляться с образцами типов, выбирая ветку по типу значения в момент выполнения. Каждый case привязывает совпавшее значение, поэтому тело работает с типизированной переменной напрямую. Это превращает длинные цепочки if/else instanceof в компактную читаемую таблицу.

static String format(Object value) {
    return switch (value) {
        case Integer i -> "int: " + i;
        case Long l    -> "long: " + l;
        case String s  -> "string: " + s;
        default        -> "other: " + value;
    };
}

switch с образцами типов должен быть исчерпывающим — он обязан покрывать каждый возможный входной вариант. Для селекторов произвольного типа Object это означает ветку default; для sealed-иерархий компилятор знает полный набор подтипов и может проверить исчерпываемость без default.

Охраняемые образцы и null

Клауза when добавляет булево условие к case, позволяя двум значениям одного типа попасть в разные ветки. Это называется охраняемым образцом, и порядок важен: более конкретные охраняемые случаи идут перед неохраняемым запасным.

static String size(String s) {
    return switch (s) {
        case String t when t.isEmpty() -> "empty";
        case String t when t.length() < 5 -> "short";
        case String t -> "long (" + t.length() + ")";
    };
}

Традиционно switch бросал NullPointerException на null-селекторе. Образцовый switch может явно обработать null с помощью case null, оставляя проверку на null внутри той же конструкции, а не в отдельном условии перед ней.

ФункцияСинтаксисНазначение
Образец типаcase String sСопоставление по типу и привязка
Охраняемый образецcase String s when s.isEmpty()Добавление условия к case
Метка nullcase nullСопоставление с null-селектором
Образец записиcase Point(int x, int y)Деструктуризация записи

Образцы записей

Образец записи сопоставляется с записью и привязывает её компоненты за один шаг, избавляя от вызовов методов-аксессоров. Поскольку записи раскрывают свои компоненты, компилятор знает точную форму и позволяет именовать каждую часть прямо на месте. Образцы записей вкладываются друг в друга, позволяя деструктурировать запись из записей.

record Point(int x, int y) {}
record Line(Point start, Point end) {}

static String render(Object o) {
    return switch (o) {
        case Point(int x, int y) -> "point " + x + "," + y;
        // Nested: pull both endpoints' coordinates out at once
        case Line(Point(int x1, int y1), Point(int x2, int y2)) ->
            "line " + x1 + "," + y1 + " -> " + x2 + "," + y2;
        default -> "unknown";
    };
}

Сопоставление с образцом особенно выразительно в сочетании с запечатанными типами: когда интерфейс перечисляет допустимые реализации, switch по ним является исчерпывающим без default, а добавление нового подтипа превращает пропущенный случай в ошибку компиляции, а не в молчаливую ошибку.

Полный работающий пример

Программа ниже объединяет все части. Она использует образец instanceof с охраной, запечатанную иерархию Shape из записей, образцы записей, деструктурирующие каждую фигуру в switch, охраняемый образец для обнаружения квадрата и case null — всё это без единого явного приведения типов.

java— editable, runs on the server

Что следует вынести из этого примера:

  • describe(42) выводит positive int 42, потому что охрана instanceof Integer i && i > 0 проверяет тип и значение вместе перед привязкой i.
  • describe(-5) проваливается в unknown — тот же образец Integer совпадает по типу, но охрана i > 0 не проходит, показывая, как охрана уточняет образец типа.
  • Ветка area не нуждается в default: Shape запечатан, поэтому перечисление Circle, Rectangle и Triangle является исчерпывающим, и компилятор удовлетворён.
  • Прямоугольник 5.0 x 5.0 выводится как square side=5.0, потому что его охраняемый случай when w == h расположен перед общим случаем Rectangle r и выигрывает.
  • Последняя строка выводит no shape: ветка case null обрабатывает null-селектор внутри switch вместо того, чтобы бросать NullPointerException.

Практика

Практика
В образцовом switch что делает добавление клаузы 'when' к case?
В образцовом switch что делает добавление клаузы 'when' к case?
Was this page helpful?