Что такое sealed class и когда её применять?

В Kotlin sealed class — это специальный тип абстрактного класса, который ограничивает множество его прямых наследников. Такие классы объявляются с помощью ключевого слова sealed и позволяют строго контролировать иерархию типов, что делает их особенно полезными для выражения ограниченных наборов состояний, например, в конечных автоматах, DSL, обработке ошибок, UI-состояниях и др.

Основная идея sealed class

Ключевая особенность sealed class — все её подклассы должны быть определены в одном и том же файле. Это позволяет компилятору знать полный перечень возможных подтипов, что делает такие классы идеальными для использования с when-выражениями — особенно без необходимости писать else.

Синтаксис

sealed class Result
data class Success(val data: String) : Result()
data class Error(val exception: Throwable) : Result()
object Loading : Result()

Здесь Result — это sealed class, и у неё три возможных наследника: Success, Error и Loading.

Использование в when

Поскольку компилятору известны все подклассы sealed class, when может быть использовано без else:

fun handle(result: Result) {
when (result) {
is Success -> println("Data: ${result.data}")
is Error -> println("Error: ${result.exception}")
is Loading -> println("Loading...")
// else не нужен  все случаи покрыты
}
}

Отличие от enum class

И sealed, и enum позволяют описать ограниченное множество состояний, но:

Aspect sealed class enum class
Наследование Поддерживает вложенные типы с состоянием Фиксированные однотипные значения
--- --- ---
Поля Могут иметь разные свойства и данные Один набор полей на все элементы
--- --- ---
Расширяемость Каждый наследник может быть полноценным классом Жёстко фиксированная структура
--- --- ---
Использование Подходит для сложных и гетерогенных состояний Простые ограниченные перечисления
--- --- ---

Применение sealed class

1. Обработка результата операций

sealed class NetworkResult
data class NetworkSuccess(val body: String) : NetworkResult()
data class NetworkFailure(val code: Int, val error: String) : NetworkResult()
object NetworkLoading : NetworkResult()

Позволяет элегантно обрабатывать различные состояния сетевого запроса.

2. Реализация состояния экрана (UI state)

sealed class UiState
object Idle : UiState()
object Loading : UiState()
data class Success(val items: List<String>) : UiState()
data class Error(val message: String) : UiState()

Такой подход облегчает поддержку однозначной архитектуры и управления отображением.

3. Алгебраические типы данных / выражения DSL

sealed class Expr
data class Const(val value: Int) : Expr()
data class Sum(val left: Expr, val right: Expr) : Expr()
data class Mul(val left: Expr, val right: Expr) : Expr()

Функция для вычисления выражения:

fun eval(expr: Expr): Int = when (expr) {
is Const -> expr.value
is Sum -> eval(expr.left) + eval(expr.right)
is Mul -> eval(expr.left) \* eval(expr.right)
}

Поддержка модификаторов

  • sealed class — всегда абстрактный, даже без ключевого слова abstract

  • Подклассы могут быть:

    • data class

    • object

    • class (своим поведением)

  • Саму sealed class нельзя создать напрямую

Наследование и размещение

  • Все непосредственные наследники должны быть в одном и том же файле.

  • Они могут находиться внутри sealed class (вложенными) или вне её (в том же файле).

  • Можно делать sealed interface (начиная с Kotlin 1.5+), аналогично по смыслу.

Отличие от abstract class

Критерий sealed class abstract class
Ограничение на подклассы Да, только в одном файле Нет ограничений
--- --- ---
Использование в when без else Да Нет
--- --- ---
Применение Моделирование ограниченного набора вариантов Базовая абстракция без ограничений
--- --- ---

Пример вложенных sealed классов

sealed class Shape {
data class Circle(val radius: Float) : Shape()
data class Rectangle(val width: Float, val height: Float) : Shape()
}

Возможность sealed interface

С Kotlin 1.5 появилась возможность создавать sealed interface:

sealed interface Event
data class Click(val x: Int, val y: Int) : Event
object Close : Event

Это удобно, когда нужен полиморфизм без конкретной реализации (наследования от класса).

sealed class особенно ценна в функциональном стиле программирования, где важно чётко выражать конечные множества возможных случаев и обрабатывать их исчерпывающим образом. Она позволяет моделировать бизнес-логику, не полагаясь на null, исключения или непредсказуемое поведение, улучшая безопасность типов и читаемость кода.