← Назад к вопросам
Как наследоваться от sealed class
1.7 Middle🔥 171 комментариев
#Kotlin основы#Архитектура и паттерны
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI21 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# Как наследоваться от sealed class в Kotlin
Что такое sealed class
Sealed class — это класс, который ограничивает иерархию наследования. Все прямые подклассы sealed class должны быть объявлены в одном файле (в Kotlin 1.0) или в одном пакете (в Kotlin 1.1+).
Это особенно полезно для моделирования ограниченных иерархий типов, когда ты знаешь заранее, какие подтипы возможны.
Правила наследования от sealed class
1. Прямые наследники должны быть в одном файле/пакете
Это основное ограничение sealed class:
// файл: sealed.kt
sealed class Result<out T> {
data class Success<T>(val data: T) : Result<T>()
data class Error(val exception: Exception) : Result<Nothing>()
object Loading : Result<Nothing>()
}
// Всё в одном файле!
2. Подклассы могут быть:
a) Data class:
sealed class Result {
data class Success(val data: String) : Result()
data class Error(val message: String) : Result()
}
val result: Result = Result.Success("Hello")
when (result) {
is Result.Success -> println(result.data) // Автоматический smart cast
is Result.Error -> println(result.message)
}
b) Regular class:
sealed class Shape {
class Circle(val radius: Float) : Shape()
class Rectangle(val width: Float, val height: Float) : Shape()
}
c) Object singleton:
sealed class Status {
object Loading : Status()
object Success : Status()
data class Error(val message: String) : Status()
}
d) Даже другой sealed class:
sealed class Expression {
sealed class BinaryOp : Expression() {
data class Add(val left: Expression, val right: Expression) : BinaryOp()
data class Subtract(val left: Expression, val right: Expression) : BinaryOp()
}
data class Literal(val value: Int) : Expression()
}
Практические примеры
Пример 1: Моделирование Result/Either
Это классический пример использования sealed class:
sealed class Result<out T> {
data class Success<T>(val data: T) : Result<T>()
data class Error(val exception: Exception) : Result<Nothing>()
object Loading : Result<Nothing>()
}
// Использование
suspend fun fetchUser(): Result<User> {
return try {
val user = api.getUser()
Result.Success(user)
} catch (e: Exception) {
Result.Error(e)
}
}
// Pattern matching
when (val result = fetchUser()) {
is Result.Success -> {
// Автоматический smart cast: result.data имеет тип User
println("User: ${result.data.name}")
}
is Result.Error -> {
// result.exception доступно
logger.error(result.exception)
}
Result.Loading -> {
// Loading не имеет поля данных
showProgressBar()
}
}
Пример 2: Моделирование UI состояния в MVVM
sealed class UiState<out T> {
object Idle : UiState<Nothing>()
object Loading : UiState<Nothing>()
data class Content<T>(val data: T) : UiState<T>()
data class Error(val error: Throwable) : UiState<Nothing>()
}
class UserViewModel : ViewModel() {
private val _uiState = MutableStateFlow<UiState<User>>(UiState.Idle)
val uiState: StateFlow<UiState<User>> = _uiState.asStateFlow()
fun loadUser(userId: String) {
viewModelScope.launch {
_uiState.value = UiState.Loading
_uiState.value = try {
UiState.Content(userRepository.getUser(userId))
} catch (e: Exception) {
UiState.Error(e)
}
}
}
}
// В UI (Compose)
@Composable
fun UserScreen(viewModel: UserViewModel) {
val uiState by viewModel.uiState.collectAsState()
when (val state = uiState) {
UiState.Idle -> Text("Start loading")
UiState.Loading -> CircularProgressIndicator()
is UiState.Content -> UserCard(state.data) // Smart cast
is UiState.Error -> ErrorMessage(state.error.message)
}
}
Пример 3: Моделирование событий
sealed class AnalyticsEvent {
data class ScreenViewed(val screenName: String) : AnalyticsEvent()
data class ButtonClicked(val buttonId: String, val timestamp: Long) : AnalyticsEvent()
data class ErrorOccurred(val errorCode: Int, val message: String) : AnalyticsEvent()
}
class AnalyticsTracker {
fun logEvent(event: AnalyticsEvent) {
when (event) {
is AnalyticsEvent.ScreenViewed -> {
// event.screenName доступно
sendToFirebase("screen_view", mapOf("name" to event.screenName))
}
is AnalyticsEvent.ButtonClicked -> {
sendToFirebase("button_click", mapOf("id" to event.buttonId))
}
is AnalyticsEvent.ErrorOccurred -> {
sendToFirebase("error", mapOf(
"code" to event.errorCode,
"message" to event.message
))
}
}
}
}
Ограничения sealed class
До Kotlin 1.1
- Прямые наследники должны быть в одном файле
- Нельзя иметь наследников в других файлах пакета
В Kotlin 1.1+
- Прямые наследники должны быть в одном пакете
- Но могут быть в разных файлах одного пакета
Всегда
- Косвенные наследники (наследники наследников) могут быть в разных пакетах
- sealed class не может быть private или internal в пределах класса
Сравнение с альтернативами
sealed class vs enum
// enum — каждое значение singleton
enum class Status {
IDLE, LOADING, SUCCESS, ERROR
}
// sealed class — каждый подкласс может иметь свои данные
sealed class Status {
object Idle : Status()
object Loading : Status()
data class Success(val data: String) : Status()
data class Error(val message: String) : Status()
}
sealed class vs open class
// open class — кто угодно может наследоваться
open class Animal { }
class Dog : Animal() { } // ✓ разрешено
class Cat : Animal() { } // ✓ разрешено
// sealed class — только известные подклассы
sealed class Animal { }
class Dog : Animal() { } // ✓ разрешено (если в одном файле)
class Cat : Animal() { } // ✓ разрешено (если в одном файле)
Best Practices
- Используй sealed class для ограниченных иерархий — когда ты знаешь все возможные подтипы
- Combine с when expression — полнота проверки гарантирована компилятором
- Используй для Result/Either типов — очень естественно для обработки ошибок
- Используй для UI состояния — чистая и type-safe модель состояния
- Не переусложняй — если иерархия может быть расширяемой, используй open class