sealed class и иерархии

Учимся описывать строго ограниченный набор вариантов одного типа.

sealed class — класс, все наследники которого известны на этапе компиляции и объявлены в том же файле (модуле).

Иногда нужно описать тип, у которого ровно несколько вариантов: результат операции — это либо успех, либо ошибка; состояние загрузки — либо «грузим», либо «готово», либо «сбой». Для таких закрытых иерархий идеален sealed class.

Закрытая иерархия

sealed запрещает создавать наследников где-либо, кроме того же файла. Компилятор знает полный список подтипов.

sealed class Result
class Success(val data: String) : Result()
class Error(val message: String) : Result()

fun handle(r: Result): String = when (r) {
    is Success -> "Данные: ${r.data}"
    is Error -> "Ошибка: ${r.message}"
}

fun main() {
    println(handle(Success("OK")))
    println(handle(Error("нет связи")))
}

Вывод:

Данные: OK
Ошибка: нет связи

Исчерпывающий when без else

Раз компилятор знает все подтипы sealed-класса, в when можно не писать else — достаточно покрыть все варианты. Более того, если вы добавите новый подтип и забудете обработать его, компилятор укажет на это.

sealed class Shape
class Circle(val r: Double) : Shape()
class Square(val side: Double) : Shape()

fun area(s: Shape): Double = when (s) {
    is Circle -> 3.14 * s.r * s.r
    is Square -> s.side * s.side
    // else не нужен — все случаи покрыты
}

fun main() {
    println(area(Circle(2.0)))
    println(area(Square(3.0)))
}

Вывод:

12.56
9.0

Где это полезно

sealed class идеально моделирует состояния и результаты: загрузка экрана (Loading/Content/Failure), исход операции (Ok/Fail), узлы дерева. Это безопаснее набора булевых флагов: невозможные комбинации просто нельзя выразить.

Как работает под капотом

В ветках when (r) { is Success -> ... } снова работает smart cast: после проверки is Success компилятор уже знает точный тип и даёт обращаться к r.data без приведения. А «исчерпывающесть» (exhaustiveness) — это статическая проверка компилятора: он сверяет список веток с полным списком наследников и требует обработать каждый, если when используется как выражение.

Частые ошибки

  • Объявлять наследников sealed-класса в другом файле/модуле. Это запрещено — теряется гарантия полноты.
  • Добавлять else «на всякий случай». Тогда компилятор перестанет напоминать о новом необработанном подтипе.
  • Путать sealed с enum. enum — фиксированный набор значений, sealed — набор подтипов, каждый со своими данными.

Итог

  • sealed class задаёт закрытый, известный компилятору набор подтипов.
  • В when по такому типу else не нужен — покрываются все случаи.
  • Компилятор подсказывает, если новый подтип не обработан.
  • Отлично подходит для моделирования состояний и результатов.
Проверьте себя
1. Чем удобен sealed class в связке с when?
AПозволяет не писать else, покрыв все известные подтипы
BУскоряет программу
CЗапрещает использовать when
DДелает класс синглтоном
2. Где можно объявлять наследников sealed class?
AВ любом месте программы
BВ том же файле/модуле, что и сам sealed class
CТолько в другом модуле
DНигде — наследники запрещены
3. Чем sealed class отличается от enum?
AНичем
Benum — фиксированный набор значений, sealed — набор подтипов со своими данными
Csealed нельзя использовать в when
Denum поддерживает наследование, sealed нет