Sealed Class — специальный тип класса в Kotlin, который ограничивает иерархию наследования фиксированным набором подтипов. Все наследники объявляются в том же файле и известны компилятору, что позволяет использовать исчерпывающий when-блок без обязательной else-ветки. По данным Kotlin Docs, 2026, запечатанные классы — ключевой механизм для представления ограниченных иерархий, таких как состояния, типы ошибок и UI-события.
Главное
Sealed Class (запечатанный класс) — это класс в Kotlin, помеченный модификатором sealed. Он определяет ограниченную иерархию типов: все возможные наследники перечислены в том же файле, и компилятор знает о каждом из них. Это отличает sealed class от обычного открытого класса, наследники которого могут быть объявлены где угодно.
Основная цель sealed class — типобезопасное представление конечного набора вариантов. Каждый наследник может иметь собственную структуру данных, что делает sealed class гибче enum. В рантайме sealed class — обычный абстрактный класс, компилятор накладывает ограничения только на этапе компиляции.
Sealed class особенно полезен в архитектуре Android приложений: состояния UI, результаты сетевым запросов, Intent-подобные события навигации и, конечно, иерархии ошибок — типичные сценарии применения.
При компиляции sealed class оптимизируется в таблицу переходов для when-выражений, что делает его производительнее цепочек if-else. В сочетании с data class каждый наследник может содержать не только состояние, но и методы, что позволяет строить самодокументируемые доменные модели без boilerplate-кода.
Sealed class также эффективен для представления конечных автоматов (state machine) в мобильных приложениях. Каждое состояние — отдельный наследник с уникальными параметрами, а переходы между состояниями контролируются через when-выражение. Компилятор гарантирует, что все возможные состояния обработаны, что исключает runtime-ошибки при изменении состояния UI или бизнес-логики.
Начинающие разработчики Kotlin часто путают sealed class и enum, поскольку оба ограничивают набор значений. Однако между ними принципиальная разница: enum — это набор констант одного типа, sealed class — иерархия разных типов.
Enum оптимален, когда все варианты — константы без дополнительной структуры. Например, дни недели, статусы заказа или типы действий без параметров. Каждое значение enum — singleton с фиксированным именем.
Sealed class нужен, когда каждый вариант имеет собственные данные. Например, ошибка сети содержит код ответа, ошибка парсинга — детали, а ошибка авторизации — сообщение. Каждый наследник sealed class — отдельный тип с уникальными полями.
// Enum — все варианты одного типа
enum class Status { LOADING, SUCCESS, ERROR }
// Sealed class — каждый вариант со своими данными
sealed class UiState<out T> {
object Loading : UiState<Nothing>()
data class Success<T>(val data: T) : UiState<T>()
data class Error(val message: String) : UiState<Nothing>()
}
С Kotlin 1.5 появилась возможность объявлять sealed interface. Это расширяет концепцию sealed на интерфейсы: sealed interface также имеет фиксированный набор реализаций, но поддерживает множественное наследование.
Sealed interface удобен, когда наследники должны реализовывать несколько контрактов одновременно. Например, UI-событие может быть одновременно кликабельным и трекируемым. С sealed class пришлось бы выбирать один базовый класс, с sealed interface наследник реализует оба.
Sealed class — это класс, поэтому каждый наследник может иметь только одного родителя. Sealed interface эту проблему решает, но не может содержать состояние. Выбор между ними зависит от задачи: нужна общая логика с полями — sealed class, нужна гибкость контрактов — sealed interface.
sealed interface ScreenEvent {
data class Refresh(val force: Boolean) : ScreenEvent
data class Navigate(val route: String) : ScreenEvent
data class ShowError(val toast: String) : ScreenEvent
}
sealed interface AnalyticsEvent {
val name: String
val params: Map<String, Any>
}
// Наследник реализует оба интерфейса
data class LoginClicked(
override val name: String = "login_click",
override val params: Map<String, Any> = emptyMap()
) : ScreenEvent, AnalyticsEvent
Одно из главных применений sealed class в мобильной разработке — типобезопасная иерархия ошибок. Вместо того чтобы бросать исключения разных типов или использовать общий Exception, sealed class собирает все возможные ошибки предметной области в один тип.
Создайте sealed class DomainError и перечислите все виды сбоев как наследники. Каждый наследник содержит только те данные, которые имеют смысл для данного типа ошибки. Компилятор гарантирует, что при обработке ошибки вы не забудете ни один вариант.
Рассмотрим приложение с авторизацией, где возможны разные сценарии сбоя: неверный пароль, блокировка аккаунта, проблема с сервером. Sealed class объединяет их в единый тип с исчерпывающей обработкой.
sealed class AuthError {
data class InvalidCredentials(
val attempts: Int
) : AuthError()
data class AccountBlocked(
val until: Long
) : AuthError()
data class NetworkFailure(
val cause: Throwable
) : AuthError()
object ServerError : AuthError()
}
fun handleError(error: AuthError): String = when (error) {
is AuthError.InvalidCredentials ->
"Осталось попыток: ${3 - error.attempts}"
is AuthError.AccountBlocked ->
"Доступ заблокирован до ${Date(error.until)}"
is AuthError.NetworkFailure ->
"Проверьте соединение: ${error.cause.localizedMessage}"
AuthError.ServerError ->
"Сервер временно недоступен"
}
Sealed class стал стандартным инструментом в архитектуре Android-приложений. Рассмотрим три ключевых паттерна, где sealed class незаменим в мобильной разработке.
Отдельно стоит отметить применение sealed class в Clean Architecture. Каждый слой (data, domain, presentation) использует sealed class для своих типов ошибок, а мапперы преобразуют один sealed class в другой. Например, DataError из слоя данных маппится в DomainError для бизнес-логики, а затем в UiState для presentation-слоя. Это сохраняет типобезопасность на всех уровнях приложения и гарантирует, что ни одна ошибка не останется необработанной.
Тестирование sealed class требует особого подхода, поскольку каждый наследник — отдельный тип с собственным состоянием. Рекомендуется писать параметризованные тесты, проходящие по всем наследникам sealed class. Это гарантирует, что when-выражения покрывают все варианты, включая новые, добавленные при расширении иерархии.
Для UI-тестов sealed class как UiState позволяет проверить отображение каждого состояния: Loading показывает спиннер, Content — данные, Error — сообщение об ошибке. Поскольку sealed class конечен, тестовое покрытие всех состояний даёт полную уверенность в корректности UI-логики.
Несмотря на простоту концепции, разработчики регулярно допускают ошибки при проектировании sealed class иерархий. Рассмотрим основные проблемы и способы их избежать.
Часто задаваемые вопросы
Да, sealed class может содержать абстрактные методы, и каждый наследник обязан их реализовать. Это удобно, когда все варианты должны предоставлять единый интерфейс, но с разной логикой выполнения.
В Java 17+ появились sealed классы и интерфейсы с модификатором sealed. Android пока поддерживает Java 17 частично, но в Kotlin-проектах sealed class доступен с Kotlin 1.0 без ограничений.
Да, один sealed class может быть наследником другого. Иерархия sealed классов остаётся конечной: компилятор знает всех наследников на каждом уровне. Это позволяет строить детальные классификации ошибок.
Sealed class не создаёт накладных расходов в рантайме. Компилятор оптимизирует when-выражения с sealed классами в таблицы переходов (tableswitch), что быстрее цепочек if-else. Производительность идентична enum.
Каждый наследник sealed class тестируется отдельно. Поскольку sealed class конечен, можно написать параметризованный тест, который проходит по всем вариантам. Это даёт полное покрытие ветвлений when-блоков.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также