Карта / ООП / Sealed classes

Sealed classes

Sealed class (Java 17) — класс, который явно задаёт список своих наследников. Раньше любой класс можно было наследовать откуда угодно — или закрыть через final (тогда наследников вообще нет). Sealed — это промежуток: наследники есть, но только строго определённые.

sealed interface Shape permits Circle, Rectangle, Triangle {}

final class Circle    implements Shape { double radius; }
final class Rectangle implements Shape { double width, height; }
final class Triangle  implements Shape { double base, height; }

Ключевое слово permits — явный список разрешённых наследников. Попытка создать другой наследник даст ошибку компиляции.

Зачем это нужно:

// Компилятор знает ВСЕ реализации Shape
// → switch может проверить, что вы не забыли ни одну
double area(Shape s) {
    return switch (s) {
        case Circle c    -> Math.PI * c.radius * c.radius;
        case Rectangle r -> r.width * r.height;
        case Triangle t  -> 0.5 * t.base * t.height;
        // default не нужен — компилятор видит что все случаи покрыты
    };
}
Копнуть глубже

Три варианта для разрешённых подклассов:

  • final — нельзя наследовать дальше (терминальный узел)
  • sealed — можно наследовать, но снова с permits (дерево типов)
  • non-sealed — снимает ограничение, любой может наследовать
sealed class Vehicle permits Car, Truck, SpecialVehicle {}
final class Car extends Vehicle {}
final class Truck extends Vehicle {}
non-sealed class SpecialVehicle extends Vehicle {}  // от неё — без ограничений

Sealed + record = алгебраические типы данных. Комбинация позволяет моделировать типы как в функциональных языках:

sealed interface Result<T> permits Result.Ok, Result.Err {
    record Ok<T>(T value)     implements Result<T> {}
    record Err<T>(String msg) implements Result<T> {}
}

Result<User> result = findUser(id);
String message = switch (result) {
    case Result.Ok<User> ok   -> "Нашли: " + ok.value().name();
    case Result.Err<User> err -> "Ошибка: " + err.msg();
};

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

Sealed классы и рефлексия. Class.permittedSubclasses() — можно получить список разрешённых наследников в рантайме. Фреймворки (Jackson, Hibernate) используют это для сериализации иерархий типов без дополнительных аннотаций.

🎤 Закрыл тему, если можешь объяснить:
• зачем sealed class и чем он отличается от final;
• как sealed + switch позволяют компилятору проверить полноту обработки (если дошёл до 2-го слоя).