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 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. 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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

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