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