Sealed классы (Java 17)
Sealed (запечатанный) класс или интерфейс — это тип, который ограничивает, какие другие классы или интерфейсы могут его наследовать или реализовывать. Фича появилась как preview в Java 15 (JEP 360) и стала стабильной в Java 17 (JEP 409).
До Java 17 у разработчика было только два крайних варианта: либо открытое наследование (public class), либо полный запрет (final). Sealed-классы дают третий вариант: «ограниченный набор подтипов, известный заранее». Это особенно полезно вместе с pattern matching: компилятор может проверить, что вы обработали все варианты в switch.
Минимальная версия: Java 17.
Синтаксис
public sealed class Parent permits Child1, Child2, Child3 { }
// Каждый наследник обязан быть final, sealed или non-sealed:
public final class Child1 extends Parent { }
public sealed class Child2 extends Parent permits SubChild { }
public non-sealed class Child3 extends Parent { }
Зачем нужно
Моделирование закрытых иерархий: «либо A, либо B, либо C, других не бывает».
Безопасный pattern matching: компилятор знает все варианты и требует обработать каждый.
Контроль API: автор библиотеки гарантирует, что внешние пользователи не подменят реализацию.
Альтернатива enum, когда у вариантов разный набор полей.
Пример 1. Базовая иерархия фигур
public class SealedShapes {
public sealed interface Shape permits Circle, Square, Triangle { }
public record Circle(double radius) implements Shape { }
public record Square(double side) implements Shape { }
public record Triangle(double base, double height) implements Shape { }
public static double area(Shape s) {
return switch (s) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Square sq -> sq.side() * sq.side();
case Triangle t -> 0.5 * t.base() * t.height();
};
}
public static void main(String[] args) {
System.out.printf("Круг: %.2f%n", area(new Circle(5)));
System.out.printf("Квадрат: %.2f%n", area(new Square(4)));
System.out.printf("Треугольник: %.2f%n", area(new Triangle(3, 6)));
}
}
Output:
Круг: 78,54
Квадрат: 16,00
Треугольник: 9,00
Пример 2. sealed + final
public class SealedFinal {
public sealed class Vehicle permits Car, Truck { }
public final class Car extends Vehicle { }
public final class Truck extends Vehicle { }
public static void main(String[] args) {
Vehicle v1 = new Car();
Vehicle v2 = new Truck();
System.out.println("v1: " + v1.getClass().getSimpleName());
System.out.println("v2: " + v2.getClass().getSimpleName());
}
}
Output:
v1: Car
v2: Truck
Пример 3. non-sealed — открыть ветку обратно
public class SealedNonSealed {
public sealed class Component permits Sensor, Actuator { }
// Sensor закрыт — ничего наследовать нельзя
public final class Sensor extends Component { }
// Actuator открыт для дальнейшего наследования
public non-sealed class Actuator extends Component { }
// Можно свободно наследовать от Actuator
public class Servo extends Actuator { }
public class Motor extends Actuator { }
public static void main(String[] args) {
Component a = new Servo();
Component b = new Motor();
System.out.println(a.getClass().getSimpleName() + " — это Actuator");
System.out.println(b.getClass().getSimpleName() + " — это Actuator");
}
}
Output:
Servo — это Actuator
Motor — это Actuator
Пример 4. Вложенная sealed-иерархия
public class SealedNested {
public sealed interface Command permits Move, Stop, Turn { }
public sealed interface Move extends Command permits Forward, Backward { }
public record Forward(int speed) implements Move { }
public record Backward(int speed) implements Move { }
public record Stop() implements Command { }
public record Turn(int angle) implements Command { }
public static String describe(Command c) {
return switch (c) {
case Forward f -> "Вперёд на скорости " + f.speed();
case Backward b -> "Назад на скорости " + b.speed();
case Stop s -> "Стоп";
case Turn t -> "Поворот на " + t.angle() + "°";
};
}
public static void main(String[] args) {
System.out.println(describe(new Forward(150)));
System.out.println(describe(new Backward(100)));
System.out.println(describe(new Stop()));
System.out.println(describe(new Turn(90)));
}
}
Output:
Вперёд на скорости 150
Назад на скорости 100
Стоп
Поворот на 90°
Пример 5. Exhaustive switch — компилятор требует все варианты
public class SealedExhaustive {
public sealed interface Result permits Ok, Err { }
public record Ok(String value) implements Result { }
public record Err(String message) implements Result { }
public static String handle(Result r) {
// default не нужен — компилятор знает, что Result либо Ok, либо Err
return switch (r) {
case Ok ok -> "OK: " + ok.value();
case Err err -> "ERR: " + err.message();
};
}
public static void main(String[] args) {
System.out.println(handle(new Ok("ультразвук = 12 см")));
System.out.println(handle(new Err("датчик не отвечает")));
}
}
Output:
OK: ультразвук = 12 см
ERR: датчик не отвечает
Пример 6. Без permits в одном файле
// Если все подтипы объявлены в том же исходном файле,
// permits можно опустить — компилятор выведет список сам.
public class SealedImplicit {
public sealed interface Signal { }
public record High() implements Signal { }
public record Low() implements Signal { }
public static void main(String[] args) {
Signal s = new High();
System.out.println("Сигнал: " + s.getClass().getSimpleName());
}
}
Output:
Сигнал: High
Подводные камни
Предупреждение
Каждый прямой наследник sealed-класса обязан быть либо
final, либоsealed, либоnon-sealed. Простоclassне подойдёт.Все наследники должны находиться в том же модуле (или в том же пакете для unnamed module).
permitsможно опустить только если все наследники объявлены в одном файле с родителем.non-sealedфактически «прокалывает дыру» в иерархии — используйте осознанно.Records неявно
finalи идеально подходят как «листья» sealed-иерархии.
Совет
Sealed-классы + records + pattern matching образуют так называемые «алгебраические типы данных» (ADT) — мощный инструмент для моделирования предметной области.
См. также
Примечание
Материал основан на официальной документации Oracle Java SE (docs.oracle.com/en/java/javase) и спецификации JEP 409 (openjdk.org/jeps/409), распространяемой под лицензией Oracle Free Documentation License. Тексты и примеры написаны заново для AlashEd Wiki.