Sealed-классы (Java 17+)
Часть раздела Объектно-ориентированное программирование путешествия по Java на Coddy — урок 52 из 87.
В традиционной Java, когда вы создаете класс, любой другой класс может его расширить (если только вы не пометите его как final). Но что, если вы хотите разрешить наследование, но не всем подряд? В Java 17 появились запечатанные классы, чтобы дать вам точный контроль над тем, какие классы могут расширять ваш.
Запечатанный класс (sealed class) явно объявляет свои разрешенные подклассы, используя ключевые слова sealed и permits:
sealed 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, так как компилятор знает все возможные подтипы и может проверить полноту охвата. Они идеально подходят для моделирования фиксированного набора типов в предметной области, таких как способы оплаты, типы ответов или геометрические фигуры.
Задание
ЛегкоДавайте создадим систему уведомлений, которая демонстрирует работу запечатанных (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). Его конструктор принимает сообщение и идентификатор устройства. Методdeliver()должен возвращать:"Sending push to device " + deviceId + ": " + message"Main.java: Объедините вашу систему уведомлений! Вы получите четыре входных значения: сообщение (String), адрес электронной почты (String), номер телефона (String) и идентификатор устройства (String).Создайте по одному экземпляру каждого типа уведомлений, используя одно и то же сообщение, но соответствующие контактные данные. Сохраните все три объекта в массиве типа
Notification[], чтобы продемонстрировать полиморфизм с запечатанными классами. Затем переберите массив и выведите результат вызоваdeliver()для каждого уведомления.
Вы получите четыре входных значения по порядку: сообщение, адрес электронной почты, номер телефона и идентификатор устройства.
Обратите внимание, как запечатанный класс ограничивает иерархию — только три разрешенных класса могут расширять Notification. Каждый подкласс должен быть объявлен как final, sealed или non-sealed. Это дает вам полный контроль над иерархией типов, сохраняя при этом возможность использования полиморфизма!
Шпаргалка
Запечатанный класс (sealed class) ограничивает список классов, которые могут его расширять, используя ключевые слова sealed и permits:
sealed class Shape permits Circle, Rectangle, Triangle {
abstract double area();
}Только классы, перечисленные в permits, могут расширять запечатанный класс. Любой другой класс, пытающийся его расширить, вызовет ошибку компиляции.
Каждый разрешенный подкласс должен использовать один из трех модификаторов:
final— предотвращает дальнейшее расширениеsealed— продолжает цепочку ограничений со своими собственными разрешенными подклассамиnon-sealed— открывает иерархию для неограниченного расширения
sealed class Vehicle permits Car, Truck { }
final class Car extends Vehicle { } // Не может быть расширен
non-sealed class Truck extends Vehicle { } // Кто угодно может расширить TruckЗапечатанные классы полезны для моделирования фиксированного набора типов в предметной области, таких как способы оплаты, типы ответов или геометрические фигуры. Они хорошо работают с сопоставлением с образцом (pattern matching) в выражениях switch, так как компилятор знает все возможные подтипы.
Попробуйте сами
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 с сообщением и email
// - SMSNotification с сообщением и phoneNumber
// - PushNotification с сообщением и deviceId
// TODO: Сохранить все три уведомления в массив Notification[]
// Это демонстрирует полиморфизм с запечатанными (sealed) классами
// TODO: Перебрать массив и вывести результат выполнения deliver()
// для каждого уведомления
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыВведение в ООПКлассы и объектыКлючевое слово thisМетодыПоля (атрибуты)Метод-конструкторПерегрузка конструкторовИтоги — Простой калькулятор4Наследование
Основы наследования (extends)Ключевое слово superПереопределение методов (@Override)Цепочка конструкторовКласс ObjectОдиночное и многоуровневое наследованиеПочему нет множественного наследования классовИтоги — Иерархия сотрудников7Специальные методы и класс Object
Метод toString()equals() и hashCode()Метод clone()compareTo() и ComparableИнтерфейс ComparatorПовторение — Кастомная сортировка2Модификаторы доступа и инкапсуляция
Обзор уровней доступаМетоды геттеры и сеттерыСокрытие данныхКлючевое слово finalИтоги — Менеджер банковского счета5Полиморфизм
Основы перегрузки методовПереопределение методов (Run-Time)Upcasting и DowncastingОператор instanceofАбстрактные классы и методыИтоги: Калькулятор фигур8Продвинутые концепции ООП
Композиция против наследованияАгрегация против композицииВнутренние, вложенные и анонимные классыEnums и методы перечисленийRecords (Java 16+)Sealed-классы (Java 17+)11Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн BuilderПаттерн ObserverПаттерн Strategy3Свойства класса и статические члены
Переменные экземпляра и статические переменныеСтатические методыСтатические блокиКонстанты (static final)Итоги: Счетчик и утилиты6Интерфейсы и абстрактные классы
Введение в интерфейсыРеализация интерфейсовРеализация нескольких интерфейсовDefault и Static методы в интерфейсахАбстрактные классы vs ИнтерфейсыФункциональные интерфейсыИтоги — Платежная система9Обобщения
Введение в обобщенияОбобщенные классыОбобщенные методыОграниченные параметры типаСимволы подстановки (Wildcards: ?, extends, super)Итоги — Обобщенный контейнер12Паттерны проектирования. Часть 2
Паттерн «Команда»Паттерн «Адаптер»Паттерн «Декоратор»Паттерн «Шаблонный метод»Паттерн «Состояние»Паттерн «Компоновщик»Паттерн «Итератор»