Как писать лучший код на Java с помощью сопоставления шаблонов и герметичных классов

Как писать лучший код на Java с помощью сопоставления шаблонов и герметичных классов

Содержание
  1. Что такое сопоставление шаблонов в Java?
    1. Традиционное сопоставление шаблонов
    2. Современное сопоставление шаблонов
  2. Что такое герметичные классы в Java?
    1. Несколько важных моментов, о которых следует помнить:
  3. Как сочетать шаблонизацию и закрытые классы в Java
  4. Что такое герметичный интерфейс в Java?
  5. Заключение

В этой статье мы рассмотрим, как можно улучшить качество кода Java с помощью Pattern Matching и Sealed Classes.

Java Pattern Matching позволяет писать более лаконичный и читабельный код при работе со сложными структурами данных. Оно упрощает извлечение данных из структур данных и выполнение операций над ними.

Давайте погрузимся в эту тему и узнаем больше о сопоставлении шаблонов и герметичных классах.

Что такое сопоставление шаблонов в Java?

При сопоставлении шаблонов значение сопоставляется с шаблоном, включающим переменные и условия. Если значение совпадает с шаблоном, соответствующие части значения привязываются к переменным в шаблоне. Это позволяет создавать более читабельный и интуитивно понятный код.

Существует два типа сопоставления шаблонов: традиционный и современный. В следующих разделах мы рассмотрим различия между ними.

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

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

Например:

private static void printGreetingBasedOnInput(String input){
switch (input){
case "hello":
System.out.println("Hi There");
break;
case "goodbye":
System.out.println("До встречи!");
break;
case "thank you":
System.out.println("Пожалуйста");
break;
default:
System.out.println("Я не понимаю");
break;
}
}

Java-метод printGreetingBasedOnInput принимает строку input и печатает соответствующее приветствие на основе ее значения с помощью оператора switch-case. Он охватывает случаи ”привет”, “до свидания” и “спасибо”, предоставляя соответствующие ответы, и по умолчанию выводит “я не понимаю” для любых других входных данных.

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

В современных шаблонах оператор 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("Я не понимаю");
        }
    }

В этом фрагменте кода используется более лаконичный синтаксис. Он упрощает код, напрямую указывая действие, которое нужно выполнить для каждой метки 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("Я не понимаю");
}
}

Усовершенствование instanceof становится особенно ценным при работе с защитниками шаблонов. Защитники шаблонов - это способ сделать операторы case в Java-сопоставлении шаблонов более конкретными за счет включения булевых выражений.

Это позволяет более тонко контролировать процесс сопоставления шаблонов и делает код более читабельным и выразительным.

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("Строка!");
        default -> System.out.println("Неверный ввод");
    }
}

На основе приведенных выше примеров вы, надеюсь, можете убедиться, что сопоставление шаблонов в Java дает различные преимущества:

  • Улучшает читаемость кода, позволяя эффективно сопоставлять значения с шаблонами и извлекать данные.
  • Оно уменьшает дублирование кода, позволяя обрабатывать различные случаи одним фрагментом кода.
  • Повышает безопасность типов, позволяя сопоставлять значения с определенными типами.
  • Защитники шаблонов можно использовать внутри случаев, чтобы еще больше повысить читаемость и удобство сопровождения кода.

Что такое герметичные классы в Java?

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

Закрытые классы позволяют создать иерархию классов или интерфейсов, которые могут быть расширены или реализованы только определенным набором классов.

Например:

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 Результат {

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

}

В этом примере мы определили герметичный класс Result, который может быть расширен классами Success или Failure.

Любой другой класс, который попытается расширить Result, приведет к ошибке компиляции.

Это позволяет ограничить набор классов, которые могут быть использованы для расширения Result, что делает код более удобным и расширяемым.

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

  • Если подкласс хочет быть разрешенным подклассом запечатанного класса в Java, он должен быть определен в том же пакете, что и запечатанный класс. Если подкласс не определен в том же пакете, произойдет ошибка компиляции.
  • Если подкласс должен быть разрешен для расширения запечатанного класса в Java, он должен иметь один из трех модификаторов: 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);
    };
}

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

Поскольку закрытые классы имеют фиксированный набор разрешенных подклассов, можно охватить все случаи с помощью конечного числа операторов 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();
};
}

Заключение

Вот несколько основных выводов об использовании Pattern Matching и Sealed Classes в Java-коде:

  • Улучшение читабельности: Pattern Matching и Sealed Classes могут сделать код более выразительным и легким для чтения, поскольку они позволяют использовать более лаконичный и интуитивно понятный синтаксис.
  • Большой контроль над иерархиями классов: Уплотненные классы позволяют контролировать иерархии классов и гарантировать, что могут использоваться только разрешенные подклассы. Это может повысить безопасность кода и удобство сопровождения.
  • Неявная безопасность типов: Pattern Matching и Sealed Classes обеспечивают неявную безопасность типов, что снижает риск ошибок во время выполнения и упрощает сопровождение кода.
  • Уменьшение дублирования кода: Pattern Matching и Sealed Classes позволяют сократить дублирование кода, позволяя обрабатывать различные случаи в одном фрагменте кода.
  • Улучшение организации кода: Sealed Classes могут помочь организовать код и уменьшить сложность иерархий классов, группируя связанные классы вместе.
  • Улучшение сопровождаемости: Pattern Matching и Sealed Classes могут улучшить сопровождаемость кода, облегчая его понимание и обновление, что в конечном итоге сэкономит время и усилия.

Большое спасибо за прочтение.