JavaRush /Java блог /Random /Кофе-брейк #260. Традиционное и современное сопоставление...

Кофе-брейк #260. Традиционное и современное сопоставление с образцом. Как объединить сопоставление с образцом с запечатанными классами. Что такое запечатанные интерфейсы

Статья из группы Random
Источник: FreeCodeCamp Эта публикация посвящена вопросу улучшения качества кода Java с помощью сопоставления с образцом и запечатанными классами. Кофе-брейк #260. Традиционное и современное сопоставление с образцом. Как объединить сопоставление с образцом с запечатанными классами. Что такое запечатанные интерфейсы - 1Pattern Matching (Сопоставление с образцом) позволяет писать более лаконичный и читаемый код Java при работе со сложными структурами данных. Также оно упрощает извлечение данных из структур данных и выполнение операций над ними.

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

Pattern Matching сопоставляет значение с шаблоном, который включает переменные и условия. Если значение соответствует шаблону, соответствующие части значения привязываются к переменным в шаблоне. Это позволяет создавать более читаемый и интуитивно понятный код. Существует два типа сопоставления с образцом: традиционный и современный. Давайте посмотрим на различия между ними.

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

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

private static void printGreetingBasedOnInput(String input){
        switch (input){
            case "hello":
                System.out.println("Hi There");
                break;
            case "goodbye":
                System.out.println("See you Later!");
                break;
            case "thank you":
                System.out.println("You are welcome");
                break;
            default:
                System.out.println("I don't understand");
                break;
        }
    }
Здесь метод printGreetingBasedOnInput принимает строку input и печатает соответствующее приветственное сообщение на основе ее значения с помощью операторов switch-case. Он охватывает случаи “hello” “goodbye” и “thank you”, предоставляет соответствующие ответы и по умолчанию использует “I don't understand” (Я не понимаю) для любых других входных данных.

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

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

private static void printGreetingBasedOnInput(String input){
        switch (input){
            case "hello" -> System.out.println("Hi There");
            case "goodbye" -> System.out.println("See you Later!");
            case "thank you" -> System.out.println("You are welcome");
            default -> System.out.println("I don't understand");
        }
    }
В этом фрагменте используется более краткий синтаксис. Это упрощает код, напрямую указывая действие, которое необходимо выполнить для каждой метки case. До Java 16 нам нужно было проверять тип объекта, а затем явно приводить его к переменной. Расширенный оператор instanceof, представленный в Java 16, может как проверять тип, так и выполнять неявное приведение к переменной, как в примере ниже:

private static void printType(Object input){
        switch (input) {
            case Integer i -> System.out.println("Integer");
            case String s -> System.out.println("String!");
            default -> System.out.println("I don't understand");
        }
    }
Улучшение instanceof становится особенно полезным при работе с защитой шаблонов (pattern guards). Защита шаблонов — это способ сделать операторы case в сопоставлении шаблонов Java более конкретными за счет включения логических выражений (boolean expressions). Это обеспечивает более точный контроль над сопоставлением шаблонов и может сделать ваш код более читабельным и выразительным.

private static void printType(Object input){                                                 
    switch (input) {                                                                         
        case Integer i && i > 10 -> System.out.println("Integer is greater than 10");        
        case String s && !s.isEmpty()-> System.out.println("String!");                       
        default -> System.out.println("Invalid Input");                                      
    }                                                                                        
}
На основе приведенных выше примеров вы можете увидеть, что сопоставление шаблонов Java предоставляет различные преимущества:
  • Оно улучшает читаемость кода, позволяя эффективно сопоставлять значения с шаблонами и извлекать данные.
  • Оно уменьшает дублирования кода, обрабатывая различные случаи (cases) с помощью одного фрагмента кода.
  • Оно повышает безопасность типов, позволяя сопоставлять значения с конкретными типами.
  • Защитники шаблонов (Pattern guards) могут использоваться в case для дальнейшего улучшения читаемости и удобства сопровождения кода.

Что такое запечатанные классы в Java?

Запечатанные классы (Sealed Classes) позволяют разработчикам ограничивать набор классов, которые могут расширять или реализовывать данный класс или интерфейс. С помощью запечатанных классов можно создать иерархию классов или интерфейсов, которую затем можно расширить или реализовать только с помощью определенного набора классов. Например:

public sealed class Result permits Success, Failure {
    protected String response;

    public String message(){
        return response;
    }
}

public final class Success extends Result {

    @Override
    public String message() {
        return "Success!";
    }
}

public final class Failure extends Result {

    @Override
    public String message() {
        return "Failure!";
    }
}
В этом примере мы определили запечатанный класс Result, который можно расширить с помощью классов Success или Failure. Любой другой класс, который попытается расширить Result, приведет к ошибке компиляции. Это дает нам возможность ограничить набор классов, которые можно использовать для расширения Result, делая код более удобным в сопровождении и расширяемым.

Несколько важных моментов, которые стоит учесть:

  • Если подкласс хочет быть разрешенным подклассом запечатанного класса в Java, он должен быть определен в том же пакете, что и запечатанный класс. Если подкласс не определен в том же пакете, то произойдет ошибка компиляции.
  • Если подклассу разрешено расширять запечатанный класс в Java, он должен иметь один из трех модификаторов: final, sealed (запечатанный) или non-sealed (незапечатанный).
  • Запечатанный подкласс должен определять тот же или более ограничительный набор разрешенных подклассов, что и его запечатанный суперкласс. Запечатанные подклассы должны быть либо final, либо sealed. Незапечатанные (non-sealed) подклассы не допускаются в качестве разрешенных подклассов запечатанного. суперкласса, и все разрешенные подклассы должны принадлежать к тому же пакету, что и запечатанный суперкласс.

Как объединить сопоставление шаблонов Java и запечатанные классы

Вы можете использовать запечатанные классы и их разрешенные подклассы в операторах switch с сопоставлением шаблонов. Это может сделать ваш код более кратким и легким для чтения. Взгляните на пример:

private static String checkResult(Result result){                                     
    return switch (result) {                                                          
        case Success s -> s.message();                                                
        case Failure f -> f.message();                                                
        default -> throw new IllegalArgumentException("Unexpected Input: " + result); 
    };                                                                                
}
В случае запечатанных классов компилятору требуется ветвь по умолчанию (default branch) для сопоставления с образцом, чтобы обеспечить охват всех возможных случаев. Поскольку запечатанные классы имеют фиксированный набор разрешенных подклассов, все случаи можно охватить с помощью конечного числа операторов case. Если ветвь по умолчанию не включена, в будущем в иерархию можно добавить новый подкласс, который не будет охватываться существующими операторами case. Это приведет к ошибке выполнения, которую будет сложно отладить. Требуя ветвь по умолчанию, компилятор гарантирует, что код является полным и охватывает все возможные случаи, даже если в будущем к иерархии запечатанных классов будут добавлены новые подклассы. Это помогает предотвратить ошибки во время выполнения и делает код более надежным и удобным в сопровождении. Если мы изменим класс Result, включив в него новый подкласс Pending, и мы не включили его в сопоставление с образцом, он будет включен в ветвь по умолчанию.

Что такое запечатанный интерфейс в Java?

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

public sealed interface OtherResult permits Pending, Timeout {
    void message();
}

public final class Pending implements OtherResult{
    @Override
    public void message() {
        System.out.println("Pending!");
    }
}

public final class Timeout implements OtherResult{
    @Override
    public void message() {
        System.out.println("Timeout!");
    }
}

private static void checkResult(OtherResult result){
        switch (result) {
            case Pending p -> p.message();
            case Timeout t -> t.message();
        };
    }

Заключение

Вот несколько ключевых выводов об использовании сопоставления с образцом и запечатанных классов в коде Java:
  • Улучшенная читаемость: сопоставление с образцом и запечатанные классы могут сделать ваш код более выразительным и легким для чтения, поскольку они обеспечивают более краткий и интуитивно понятный синтаксис.
  • Лучший контроль над иерархиями классов. Запечатанные классы позволяют iконтролировать иерархии классов и гарантировать возможность использования только разрешенных подклассов. Это может улучшить безопасность и удобство сопровождения кода.
  • Неявная безопасность типов. Сопоставление с образцом и запечатанные классы обеспечивают неявную безопасность типов, что может снизить риск ошибок во время выполнения и упростить поддержку кода.
  • Уменьшение дублирования кода. Сопоставление с образцом и запечатанные классы могут уменьшить дублирование кода, позволяя обрабатывать различные случаи в одном фрагменте кода.
  • Лучшая организация кода. Запечатанные классы могут помочь организовать код и уменьшить сложность иерархии классов за счет группировки связанных классов.
  • Улучшение удобства сопровождения. Сопоставление с образцом и запечатанные классы могут улучшить удобство сопровождения кода, упрощая его понимание и обновление, что может сэкономить время и усилия в долгосрочной перспективе.
Большое спасибо за чтение.
Комментарии (1)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Алексей Уровень 4 Expert
17 октября 2023
Хорошая статья.