Sealed-классы (Java 17+)
Часть раздела Объектно-ориентированное программирование путешествия по Java на Coddy. Урок 52 из 87.
В традиционном Java, когда вы создаёте class, любой другой class может расширить его (если только вы не пометите его как final). Но что, если вы хотите разрешить наследование, только не от кого угодно? В Java 17 появились запечатанные classes, чтобы предоставить вам точный контроль над тем, какие classes могут расширять ваш class.
Запечатанный класс явно объявляет свои разрешённые подклассы с помощью ключевых слов 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. Любой другой class, пытающийся расширить его, вызовет ошибку компиляции.
Каждый разрешённый подкласс должен использовать один из трёх модификаторов: final (дальнейшее расширение запрещено), sealed (цепочка ограничений продолжается) или non-sealed (открывает возможность неограниченного расширения):
sealed class Vehicle permits Car, Truck { }
final class Car extends Vehicle { } // Не может быть расширен
non-sealed class Truck extends Vehicle { } // Любой может расширить TruckSealed classes особенно мощны в сочетании с сопоставлением с образцом в выражениях switch, поскольку компилятор знает все возможные подтипы и может проверить полноту обработки. Они идеально подходят для моделирования фиксированного набора типов в предметной области, например способов оплаты, типов ответов или геометрических фигур.
Задание
ЛегкоДавайте создадим систему уведомлений, которая демонстрирует sealed classes, ограничивая типы уведомлений, которые могут существовать в вашем приложении. Вы создадите иерархию, в которой разрешены только определённые типы уведомлений, а каждый тип обрабатывает доставку по-своему.
Вы организуете код в четырёх файлах:
Notification.java: Создайте sealed abstract class, которая служит base для всех уведомлений. Она должна разрешать ровно три подкласса:EmailNotification,SMSNotificationиPushNotification. Ваш sealed class должен содержать protected fieldmessage(String), constructor для его initialize, getter для message и abstract methoddeliver(), который возвращает String с описанием способа отправки уведомления.EmailNotification.java: Создайтеfinalclass, которая extends Notification. EmailNotification содержит дополнительный private fieldrecipient(String) для адреса электронной почты. Его constructor должен принимать и message, и recipient, вызывая parent constructor с помощьюsuper(). Методdeliver()должен возвращать:Sending email to [recipient]: [message]SMSNotification.java: Создайтеnon-sealedclass, которая extends Notification. Это позволяет другим классам при необходимости extend её в future. SMSNotification содержит дополнительный private fieldphoneNumber(String). Его constructor принимает message и номер телефона. Методdeliver()должен возвращать:Sending SMS to [phoneNumber]: [message]PushNotification.java: Создайтеfinalclass, которая extends Notification. PushNotification содержит дополнительный private fielddeviceId(String). Его constructor принимает message и идентификатор устройства. Методdeliver()должен возвращать:Sending push to device [deviceId]: [message]Main.java: Объедините вашу систему уведомлений! Вы получите четыре inputs: message (String), адрес электронной почты (String), номер телефона (String) и идентификатор устройства (String).Создайте по одному объекту каждого типа уведомления, используя один и тот же message, но соответствующую контактную информацию. Сохраните все три объекта в array типа
Notification[], чтобы продемонстрировать полиморфизм с помощью sealed classes. Затем Iterate через array и выведите результат вызоваdeliver()для каждого уведомления.
Вы получите четыре inputs в следующем порядке: message, адрес электронной почты, номер телефона и идентификатор устройства.
Обратите внимание, как sealed class ограничивает иерархию: только три разрешённых class могут extend 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()
// для каждого уведомления
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
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
Паттерн «Команда»Паттерн «Адаптер»Паттерн «Декоратор»Паттерн «Шаблонный метод»Паттерн «Состояние»Паттерн «Компоновщик»Паттерн «Итератор»Потренируйтесь самостоятельно: Онлайн-компилятор Java