Сопоставление шаблонов в Java: изменение правил игры для вашего кода

В этой статье мы рассмотрим основы сопоставления с образцом в Java (до Java 19) и узнаем, как использовать его в наших проектах.

О чем все это?

Не путать с сопоставлением регулярных выражений. Сопоставление с образцом — это метод, который позволяет нам сопоставлять определенные шаблоны или формы данных. Слово «шаблон» относится к форме или структуре данных, а «сопоставление» относится к процессу идентификации и реагирования на эту конкретную форму. Другими словами, мы сопоставляем форму данных с типом или значением.

В Java сопоставление с образцом является частью проекта Amber OpenJDK, того же проекта, который принес нам текстовые блоки, записи и выражения переключения. На сайте проекта вы можете отслеживать статус JEP, связанных с сопоставлением с образцом и другими.

Более конкретно, есть несколько шаблонов для сопоставления:

  1. Типы: сопоставление ссылки с типом.
  2. Записи: сопоставление со структурой записи.
  3. Произвольные классы: сопоставление со структурой любого класса.
  4. Массивы: сопоставление со структурой/содержимым массива.

Мы увидим примеры первых двух, поскольку мы можем протестировать их в режиме предварительного просмотра Java (19 на момент написания).

Зачем мне это нужно?

В основном эта функция позволяет создавать более эффективный и выразительный код, устраняя необходимость в подробных операторах if-else и приведении типов, как мы увидим в примерах позже. Это также обеспечивает дополнительную проверку компилятора, что означает меньшее количество потенциальных ошибок во время выполнения.

Согласно José Paumard, сопоставление с образцом станет третьей революцией в языке java после введения дженериков в java 5 и типов функций в java 8.

Где я могу его использовать?

случай

В JEP 394, выпущенном в Java 16, доступно сопоставление шаблонов instanceof.

Во-первых, давайте рассмотрим пример сопоставления шаблонов типов в экземпляре. В следующем примере мы можем увидеть синтаксис, технически говоря, мы говорим, что мы сопоставляем ссылку типа Object с шаблонами «Integer i» и «Double d». соответственно.

    static void handleNumber(Object o) {
        if (o == null) {
            System.out.println("got a null");
        }
        if (o instanceof Integer i) {
            handleInteger(i);
        } else if (o instanceof Double d) {
            handleDouble(d);
        } else {
            System.out.println("unknown value");
        }
    }

Область действия i и d находится в пределах области действия if, если только она не инвертирована следующим образом:

    void scopeExample(Object o) {
        if (!(o instanceof Number n)) {
            return;
        }
        // n available here
        System.out.println(n);
    }

Далее рассмотрим сопоставление шаблона записи всегда в контексте экземпляра. Это все еще находится в предварительной версии (JEP 405 и 432).

    record Pair(String k, String v) {
    }

    private static void handleObject(Object o) {
        // we can omit p if we're not going to use it
        if (o instanceof Pair(String k, String v) p) {
            System.out.printf("got a Pair with k %s and v : %s%n", k, v);
            System.out.printf("pair : %s%n", p);
        }
    }

Как мы видим, мы сопоставили o с шаблоном записи «Pair(String k, String v) p», это похоже на деструктурирование объектов в JavaScript. kи vбудут ссылаться на поля записи, а pбудет ссылаться на соответствующий объект как Pairrecord. Этот синтаксис также поддерживает вложенность, поэтому, если у нас есть запись, которая имеет другую запись в качестве атрибута, она также может быть сопоставлена. Тем не менее, слишком много вложенности в этом случае не является хорошей идеей.

Случай переключения

Это все еще функция предварительного просмотра, начиная с java 17 (JEP 406), и 4-й предварительный просмотр появится в JEP 433, запланированном с java 20. Если мы перепишем первый пример с переключателем, мы получим следующее:

    static void handleNumber(Object o) {
        switch (o) {
            case null -> System.out.println("got a null");
            case Integer i -> handleInteger(i);
            case Double d -> handleDouble(d);
            default -> System.out.println("unknown value");
        }
    }

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

Несколько замечаний о сопоставлении с образцом в случаях переключения:

  1. Мы можем легко обработать случай null (первый случай в предыдущем коде).
  2. Компилятор может проверить полноту случаев. Например, предположим, что мы переключаемся на запечатанный тип (т. е. подтипы известны во время компиляции). Если случаи исчерпывающие, нет необходимости в ветке по умолчанию, иначе компилятор предупредит нас о пропущенных случаях.
  3. Когда один случай охватывает следующие случаи, это означает, что он является родительским классом для следующих случаев, как в приведенном ниже примере:
    static void handleNumberWithParentClass(Object o) {
        switch (o) {
            case null -> System.out.println("got a null");
            case Number n -> System.out.println("");
            case Integer i -> handleInteger(i);
            case Double d -> handleDouble(d);
            default -> System.out.println("unknown value");
        }
    }

компилятор будет жаловаться, что «В метке доминирует предшествующая метка регистра «Число n»».

4. Уточнение случая, как описано в JEP, позволяет нам добавить условие к случаю, как показано ниже.

    static void handleNumberWithCaseRefinement(Object o) {
        switch (o) {
            case null -> System.out.println("got a null");
            case Integer i when i > 10 -> handleInteger(i);
            case Double d -> handleDouble(d);
            default -> System.out.println("unknown value");
        }
    }

Код этих примеров можно найти по адресу https://github.com/maximz101/pattern-matching-java. Попробуйте сами.

Рекомендации

https://openjdk.org/projects/amber/
https://youtu.be/aKaw9W789wU
https://youtu.be/lBOwPYvdvLQ
https ://youtu.be/gFqzFlhEJaY