Sealed Class — что это такое, принцип работы и применение

Автор: IT Sectr Опубликовано: 2026-05-26 Время чтения: 8 мин

Sealed Class — специальный тип класса в Kotlin, который ограничивает иерархию наследования фиксированным набором подтипов. Все наследники объявляются в том же файле и известны компилятору, что позволяет использовать исчерпывающий when-блок без обязательной else-ветки. По данным Kotlin Docs, 2026, запечатанные классы — ключевой механизм для представления ограниченных иерархий, таких как состояния, типы ошибок и UI-события.

Главное

  • Sealed Class — класс с фиксированным набором наследников, объявленных в том же файле.
  • Exhaustive when — компилятор проверяет, что обработаны все подтипы, исключая забытые else-ветки.
  • Sealed interface — Kotlin 1.5+ поддерживает sealed интерфейсы для множественного наследования.
  • Иерархия ошибок — sealed class — стандартный способ типобезопасной обработки ошибок в Kotlin.
  • Отличие от enum — каждый наследник sealed class может содержать уникальное состояние и разное количество полей.

Что такое Sealed Class?

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 или бизнес-логики.

Sealed Class и Enum: ключевые отличия

Начинающие разработчики Kotlin часто путают sealed class и enum, поскольку оба ограничивают набор значений. Однако между ними принципиальная разница: enum — это набор констант одного типа, sealed class — иерархия разных типов.

Когда выбрать enum

Enum оптимален, когда все варианты — константы без дополнительной структуры. Например, дни недели, статусы заказа или типы действий без параметров. Каждое значение enum — singleton с фиксированным именем.

Когда выбрать sealed class

Sealed class нужен, когда каждый вариант имеет собственные данные. Например, ошибка сети содержит код ответа, ошибка парсинга — детали, а ошибка авторизации — сообщение. Каждый наследник sealed class — отдельный тип с уникальными полями.

kotlin
// 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>()
}

Sealed Interface против Sealed Class

С Kotlin 1.5 появилась возможность объявлять sealed interface. Это расширяет концепцию sealed на интерфейсы: sealed interface также имеет фиксированный набор реализаций, но поддерживает множественное наследование.

Когда использовать sealed interface

Sealed interface удобен, когда наследники должны реализовывать несколько контрактов одновременно. Например, UI-событие может быть одновременно кликабельным и трекируемым. С sealed class пришлось бы выбирать один базовый класс, с sealed interface наследник реализует оба.

Ограничения sealed class

Sealed class — это класс, поэтому каждый наследник может иметь только одного родителя. Sealed interface эту проблему решает, но не может содержать состояние. Выбор между ними зависит от задачи: нужна общая логика с полями — sealed class, нужна гибкость контрактов — sealed interface.

kotlin
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 для иерархии ошибок в мобильной разработке

Одно из главных применений sealed class в мобильной разработке — типобезопасная иерархия ошибок. Вместо того чтобы бросать исключения разных типов или использовать общий Exception, sealed class собирает все возможные ошибки предметной области в один тип.

Как построить иерархию ошибок

Создайте sealed class DomainError и перечислите все виды сбоев как наследники. Каждый наследник содержит только те данные, которые имеют смысл для данного типа ошибки. Компилятор гарантирует, что при обработке ошибки вы не забудете ни один вариант.

Пример: обработка ошибок аутентификации

Рассмотрим приложение с авторизацией, где возможны разные сценарии сбоя: неверный пароль, блокировка аккаунта, проблема с сервером. Sealed class объединяет их в единый тип с исчерпывающей обработкой.

kotlin
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 стал стандартным инструментом в архитектуре Android-приложений. Рассмотрим три ключевых паттерна, где sealed class незаменим в мобильной разработке.

  • UI State — представление экрана как конечного автомата: Loading, Content, Error. Каждое состояние содержит свои данные, а sealed class гарантирует, что все переходы обработаны.
  • Navigation Event — sealed class вместо навигационных констант: каждый экран — отдельный наследник с параметрами маршрута. Компилятор проверяет типы аргументов.
  • Action/Intent — паттерн Unidirectional Data Flow использует sealed class для представления всех действий, которые может выполнить пользователь на экране.

Отдельно стоит отметить применение sealed class в Clean Architecture. Каждый слой (data, domain, presentation) использует sealed class для своих типов ошибок, а мапперы преобразуют один sealed class в другой. Например, DataError из слоя данных маппится в DomainError для бизнес-логики, а затем в UiState для presentation-слоя. Это сохраняет типобезопасность на всех уровнях приложения и гарантирует, что ни одна ошибка не останется необработанной.

Тестирование sealed class иерархий

Тестирование sealed class требует особого подхода, поскольку каждый наследник — отдельный тип с собственным состоянием. Рекомендуется писать параметризованные тесты, проходящие по всем наследникам sealed class. Это гарантирует, что when-выражения покрывают все варианты, включая новые, добавленные при расширении иерархии.

Для UI-тестов sealed class как UiState позволяет проверить отображение каждого состояния: Loading показывает спиннер, Content — данные, Error — сообщение об ошибке. Поскольку sealed class конечен, тестовое покрытие всех состояний даёт полную уверенность в корректности UI-логики.

Типичные ошибки при работе с Sealed Class

Несмотря на простоту концепции, разработчики регулярно допускают ошибки при проектировании sealed class иерархий. Рассмотрим основные проблемы и способы их избежать.

  • Наследники в разных файлах — компилятор не позволит объявить sealed class, если наследники находятся за пределами файла. Это ограничение гарантирует исчерпывающий when.
  • Смешивание sealed и open — sealed class не может быть open одновременно. Если нужна расширяемая иерархия, используйте обычный abstract class, но пожертвуете exhaustiveness.
  • Избыточная вложенность — sealed class в sealed class создаёт глубокую иерархию, которую сложно поддерживать. Для простых сценариев достаточно двух уровней.
  • Забытый else в when — если sealed class из библиотеки без exhaustiveness, компилятор не предупредит о пропущенной ветке. Добавляйте else только осознанно.

Часто задаваемые вопросы

Может ли sealed class иметь абстрактные методы?

Да, sealed class может содержать абстрактные методы, и каждый наследник обязан их реализовать. Это удобно, когда все варианты должны предоставлять единый интерфейс, но с разной логикой выполнения.

Доступны ли sealed class в Java?

В Java 17+ появились sealed классы и интерфейсы с модификатором sealed. Android пока поддерживает Java 17 частично, но в Kotlin-проектах sealed class доступен с Kotlin 1.0 без ограничений.

Может ли sealed class наследоваться от другого sealed class?

Да, один sealed class может быть наследником другого. Иерархия sealed классов остаётся конечной: компилятор знает всех наследников на каждом уровне. Это позволяет строить детальные классификации ошибок.

Влияет ли sealed class на производительность?

Sealed class не создаёт накладных расходов в рантайме. Компилятор оптимизирует when-выражения с sealed классами в таблицы переходов (tableswitch), что быстрее цепочек if-else. Производительность идентична enum.

Как тестировать sealed class иерархии?

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

Итоги

  • Sealed class — класс с фиксированным набором наследников, объявленных в одном файле, что даёт исчерпывающий when-анализ на этапе компиляции.
  • Каждый наследник sealed class может иметь собственную структуру данных — это главное отличие от enum, где все варианты — константы одного типа.
  • Sealed interface (Kotlin 1.5+) поддерживает множественное наследование, sealed class — только одиночное. Выбор зависит от потребности в общем состоянии.
  • Sealed class — стандартный механизм для типобезопасной иерархии ошибок в Kotlin: каждый тип сбоя — отдельный наследник с релевантными полями.
  • Основные паттерны в Android: UI State, Navigation Event и Action/Intent — строятся на sealed class для гарантии полноты обработки.
  • Избегайте наследников в разных файлах, избыточной вложенности и смешивания sealed с open — это нарушает контракт конечной иерархии.

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также