Data-классы и sealed-классы
data class генерирует equals, hashCode, toString и copy автоматически, а sealed class задаёт закрытую иерархию типов — идеальный инструмент для моделирования состояний экрана.
Суть: data-классы избавляют от шаблонного кода для моделей данных, а sealed-классы делают ветвление по состояниям исчерпывающим и безопасным.
Очень часто класс нужен только для того, чтобы хранить набор полей: пользователь, товар, сообщение. Для этого есть data class. Достаточно объявить его с параметрами — и компилятор сам сгенерирует equals/hashCode (сравнение по значениям полей), toString (читаемый вывод) и copy (копия с изменением части полей).
data class User(val name: String, val age: Int)
val u1 = User("Аня", 19)
val u2 = User("Аня", 19)
println(u1 == u2) // true: сравнение по значениям
println(u1) // User(name=Аня, age=19)
val older = u1.copy(age = 20) // копия с новым возрастом
println(older) // User(name=Аня, age=20)Метод copy особенно важен в Android: состояние экрана обычно описывают неизменяемым data-классом, а при изменении создают копию с новыми значениями. Это основа предсказуемого обновления UI в Compose.
Деструктуризация
Data-классы поддерживают разбор на отдельные переменные одной строкой.
val (name, age) = User("Иван", 21)
println(name) // Иван
println(age) // 21Sealed-классы
sealed class описывает закрытую иерархию: все наследники известны на этапе компиляции и обычно лежат в том же файле. Это идеально для моделирования ограниченного набора состояний — например, загрузка данных может быть в состоянии «идёт загрузка», «успех с данными» или «ошибка».
sealed class UiState {
object Loading : UiState()
data class Success(val data: List<String>) : UiState()
data class Error(val message: String) : UiState()
}
fun render(state: UiState): String = when (state) {
is UiState.Loading -> "Загрузка..."
is UiState.Success -> "Данные: ${state.data}"
is UiState.Error -> "Ошибка: ${state.message}"
// else не нужен: when исчерпывающий
}Как работает под капотом
Поскольку компилятор знает полный список наследников sealed-класса, он проверяет when на исчерпывающность. Если вы обработали не все варианты, код не скомпилируется (для when-выражения) или вы получите предупреждение. Это бесценно: добавив новое состояние, вы сразу увидите все места, которые нужно обновить. Внутри smart cast после is UiState.Success даёт прямой доступ к полю data без явного приведения.
sealed class UiState
|
+----+--------+--------+
v v v
Loading Success Error
(object) (data:...) (msg:...)
when(state) обязан покрыть все три веткиЧастые ошибки
Использовать обычный класс там, где нужен data. Без data сравнение идёт по ссылке, а не по значениям, и toString выводит мусор.
Менять поля копии вместо создания новой. Состояние держите неизменяемым и применяйте copy — это основа корректной работы Compose.
Добавлять else в when по sealed без нужды. Лишний else прячет ситуацию, когда вы забыли обработать новое состояние.
Best practices
- Для моделей данных используйте
data classс неизменяемымиval-полями. - Обновляйте состояние через
copy, а не мутацией полей. - Моделируйте состояния экрана через
sealed classи обрабатывайте их исчерпывающимwhen. - Избегайте
elseв when по sealed, чтобы компилятор ловил необработанные случаи.
Идею sealed-состояний и исчерпывающего ветвления удобно прорепетировать на Python-редьюсере. Запустите врезку.
# Аналог sealed UiState + when из Kotlin
def render(state):
kind = state['kind']
if kind == 'loading': return 'Загрузка...'
if kind == 'success': return 'Данные: ' + str(state['data'])
if kind == 'error': return 'Ошибка: ' + state['message']
raise ValueError('Неизвестное состояние: ' + kind)
states = [
{'kind': 'loading'},
{'kind': 'success', 'data': ['a', 'b']},
{'kind': 'error', 'message': 'нет сети'},
]
for s in states:
print(render(s))Попробуй сам ▶ — добавьте новое состояние (например, пустой список) и обработайте его. В Kotlin компилятор сам напомнил бы, что новое состояние нужно обработать в when.
Итог: data-классы убирают шаблонный код моделей и дают незаменимый copy, а sealed-классы делают состояния экрана типобезопасными. Эта связка — фундамент архитектуры современного Android-приложения на Compose.