Menu
Coddy logo textTech

Sealed-классы (Java 17+)

Часть раздела Объектно-ориентированное программирование путешествия по Java на Coddy — урок 52 из 87.

В традиционной Java, когда вы создаете класс, любой другой класс может его расширить (если только вы не пометили его как final). Но что, если вы хотите разрешить наследование, но не всем подряд? В Java 17 появились запечатанные классы (sealed classes), чтобы дать вам точный контроль над тем, какие классы могут расширять ваш.

Запечатанный класс явно объявляет свои разрешенные подклассы, используя ключевые слова sealed и permits:

sealed abstract class Shape permits Circle, Rectangle, Triangle {
    abstract double area();
}

final class Circle extends Shape {
    double radius;
    double area() { return Math.PI * radius * radius; }
}

final class Rectangle extends Shape {
    double width, height;
    double area() { return width * height; }
}

final class Triangle extends Shape {
    double base, height;
    double area() { return 0.5 * base * height; }
}

Только Circle, Rectangle и Triangle могут расширять Shape. Любой другой класс, пытающийся расширить его, вызовет ошибку компиляции.

Каждый разрешенный подкласс должен использовать один из трех модификаторов: final (дальнейшее расширение запрещено), sealed (продолжает цепочку ограничений) или non-sealed (открывает возможность для неограниченного расширения):

sealed class Vehicle permits Car, Truck { }

final class Car extends Vehicle { }           // Не может быть расширен
non-sealed class Truck extends Vehicle { }    // Кто угодно может расширить Truck

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

challenge icon

Задание

Легко

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

Вы организуете свой код в четырех файлах:

  • Notification.java: Создайте запечатанный абстрактный класс (sealed abstract class), который служит базой для всех уведомлений. Он должен разрешать ровно три подкласса: EmailNotification, SMSNotification и PushNotification. Вашему запечатанному классу требуется защищенное поле message (String), конструктор для его инициализации, геттер для сообщения и абстрактный метод deliver(), который возвращает String, описывающую способ отправки уведомления.
  • EmailNotification.java: Создайте final класс, который расширяет Notification. EmailNotification имеет дополнительное приватное поле recipient (String) для адреса электронной почты. Его конструктор должен принимать и сообщение, и получателя, вызывая родительский конструктор с помощью super(). Метод deliver() должен возвращать: "Sending email to " + recipient + ": " + message
  • SMSNotification.java: Создайте non-sealed класс, который расширяет Notification — это позволит другим классам расширять его в будущем, если потребуется. SMSNotification имеет дополнительное приватное поле phoneNumber (String). Его конструктор принимает сообщение и номер телефона. Метод deliver() должен возвращать: "Sending SMS to " + phoneNumber + ": " + message
  • PushNotification.java: Создайте final класс, который расширяет Notification. PushNotification имеет дополнительное приватное поле deviceId (String). Его конструктор принимает сообщение и ID устройства. Метод deliver() должен возвращать: "Sending push to device " + deviceId + ": " + message
  • Main.java: Объедините вашу систему уведомлений! Вы получите четыре входных значения: сообщение (String), адрес электронной почты (String), номер телефона (String) и ID устройства (String).

    Создайте по одному экземпляру каждого типа уведомлений, используя одно и то же сообщение, но соответствующие контактные данные. Сохраните все три объекта в массиве типа Notification[], чтобы продемонстрировать полиморфизм с запечатанными классами. Затем переберите массив и выведите результат вызова deliver() для каждого уведомления.

Вы получите четыре входных значения по порядку: сообщение, адрес электронной почты, номер телефона и ID устройства.

Обратите внимание, как запечатанный класс ограничивает иерархию — только три разрешенных класса могут расширять Notification. Каждый подкласс должен быть объявлен как final, sealed или non-sealed. Это дает вам полный контроль над иерархией типов, сохраняя при этом возможность использования полиморфизма!

Попробуйте сами

import java.util.Scanner;

class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        // Прочитать четыре входных значения
        String message = scanner.nextLine();
        String email = scanner.nextLine();
        String phoneNumber = scanner.nextLine();
        String deviceId = scanner.nextLine();
        
        // TODO: Создать по одному экземпляру каждого типа уведомления, используя одно и то же сообщение
        // - EmailNotification с message и email
        // - SMSNotification с message и phoneNumber
        // - PushNotification с message и deviceId
        
        // TODO: Сохранить все три уведомления в массив Notification[]
        // Это демонстрирует полиморфизм с использованием sealed классов
        
        // TODO: Пройти циклом по массиву и вывести результат deliver()
        // для каждого уведомления
        
    }
}
quiz iconПроверьте себя

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

Все уроки раздела Объектно-ориентированное программирование