Что такое data class в Kotlin? Какие методы генерируются автоматически?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
# Data class в Kotlin
Что такое data class
Data class — это специальный тип класса в Kotlin, предназначенный для хранения данных. Компилятор автоматически генерирует рутинные методы на основе свойств, объявленных в primary конструкторе.
Это решает проблему "boilerplate code" в Java, где нужно вручную писать equals(), hashCode(), toString(), copy().
// Декларируем
data class User(val id: Int, val name: String, val email: String)
// Компилятор генерирует все методы автоматически
Автоматически генерируемые методы
1. equals()
Проверяет структурное равенство объектов (по значениям свойств, не по ссылке).
data class User(val id: Int, val name: String)
val user1 = User(1, "Alice")
val user2 = User(1, "Alice")
val user3 = User(2, "Bob")
user1 == user2 // true (структурное равенство)
user1 === user2 // false (разные объекты в памяти)
user1 == user3 // false (разные значения)
Как работает:
// Генерируемый код
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is User) return false
return id == other.id && name == other.name
}
2. hashCode()
Вычисляет хеш-код на основе значений свойств. Нужен для использования в HashSet, HashMap и т.д.
data class User(val id: Int, val name: String)
val user1 = User(1, "Alice")
val user2 = User(1, "Alice")
user1.hashCode() == user2.hashCode() // true (одинаковые данные)
// Можно использовать в Set без дубликатов
val users = setOf(user1, user2)
println(users.size) // 1 (они считаются одним элементом)
// Или в HashMap
val userMap = mapOf(
user1 to "Alice's data",
user2 to "Bob's data" // Перезапишет user1, т.к. равны
)
Как работает:
// Генерируемый код
override fun hashCode(): Int {
var result = id.hashCode()
result = 31 * result + name.hashCode()
return result
}
3. toString()
Возвращает строковое представление объекта в формате ClassName(field1=value1, field2=value2, ...).
data class User(val id: Int, val name: String, val email: String)
val user = User(1, "Alice", "alice@example.com")
println(user) // User(id=1, name=Alice, email=alice@example.com)
// Очень удобно для логирования и дебага
logger.info("User created: $user")
Генерируемый код:
override fun toString(): String =
"User(id=$id, name=$name, email=$email)"
4. copy()
Создаёт новый экземпляр с изменением некоторых свойств (остальные скопируются). Очень полезно для immutable объектов.
data class User(val id: Int, val name: String, val email: String)
val user = User(1, "Alice", "alice@example.com")
// Создать копию с изменением одного поля
val updatedUser = user.copy(email = "alice.new@example.com")
println(updatedUser) // User(id=1, name=Alice, email=alice.new@example.com)
// Оригинальный объект не изменился
println(user) // User(id=1, name=Alice, email=alice@example.com)
Практическое применение:
data class AppSettings(
val theme: String,
val fontSize: Int,
val autoSync: Boolean
)
fun updateSettings(current: AppSettings, newTheme: String): AppSettings {
return current.copy(theme = newTheme) // Immutable update
}
5. componentN() функции (деструктуризация)
Генерируются функции component1(), component2(), ... для деструктуризации.
data class Point(val x: Int, val y: Int)
val point = Point(10, 20)
// Деструктуризация
val (x, y) = point
println("$x, $y") // 10, 20
// Используется в when
when (point) {
Point(0, 0) -> println("Origin")
Point(x, y) -> println("Point($x, $y)")
}
// Используется в forEach
data class Pair(val first: String, val second: Int)
val pairs = listOf(Pair("a", 1), Pair("b", 2))
pairs.forEach { (key, value) -> println("$key -> $value") }
Генерируемый код:
operator fun component1(): Int = x
operator fun component2(): Int = y
Правила для data class
1. Primary конструктор обязателен
// ✓ Правильно
data class User(val name: String)
// ✗ Неправильно — нет primary конструктора
data class User { }
2. Свойства должны быть в primary конструкторе
// ✓ Только эти свойства участвуют в equals/hashCode/toString/copy
data class User(val id: Int, val name: String) {
val createdAt = System.currentTimeMillis() // Не участвует!
}
val user1 = User(1, "Alice")
val user2 = User(1, "Alice")
user1 == user2 // true (createdAt не сравнивается)
3. Минимум одно свойство
// ✓ Нормально
data class User(val name: String)
// Не запрещено, но странно
data class Empty()
4. Не может быть abstract, open, sealed, inner
// ✗ Синтаксическая ошибка
data class User(val name: String) // По умолчанию final
// ✓ Но может наследоваться от класса
data class User(val name: String) : Entity()
5. val для immutability (best practice)
// ✓ Рекомендуется
data class User(val id: Int, val name: String)
// Не запрещено, но нарушает immutability
data class User(var id: Int, var name: String)
Практические примеры
Пример 1: API модели
data class UserResponse(
val id: Int,
val name: String,
val email: String,
val avatar: String? = null
)
// Из JSON в объект
val user = json.decodeFromString<UserResponse>(jsonString)
// Логирование
logger.info("Received: $user")
// Сравнение
if (user == previousUser) {
println("No changes")
}
Пример 2: ViewModel state (MVVM)
data class UserListState(
val isLoading: Boolean = false,
val users: List<User> = emptyList(),
val error: String? = null
)
class UserViewModel : ViewModel() {
private val _state = MutableStateFlow(UserListState())
val state: StateFlow<UserListState> = _state.asStateFlow()
fun loadUsers() {
_state.value = _state.value.copy(isLoading = true)
viewModelScope.launch {
try {
val users = userRepository.getUsers()
_state.value = _state.value.copy(
isLoading = false,
users = users,
error = null
)
} catch (e: Exception) {
_state.value = _state.value.copy(
isLoading = false,
error = e.message
)
}
}
}
}
Пример 3: Deconstructing в функциях
data class Credentials(val username: String, val password: String)
fun authenticate(credentials: Credentials): Boolean {
val (username, password) = credentials
return authService.verify(username, password)
}
// Или в когда-то параметры
fun processCredentials(credentials: Credentials) {
when (credentials) {
Credentials("admin", "password") -> println("Admin logged in")
Credentials(username, password) -> println("User $username logged in")
}
}
Data class vs Regular class
// Regular class — нужно писать методы вручную
class User(val id: Int, val name: String) {
override fun equals(other: Any?): Boolean { ... }
override fun hashCode(): Int { ... }
override fun toString(): String { ... }
}
// Data class — всё генерируется автоматически
data class User(val id: Int, val name: String)
Когда использовать data class
- ✓ Модели данных (Entity, DTO, ViewModel state)
- ✓ Результаты функций (Result, Either)
- ✓ Конфигурационные объекты
- ✓ Параметры функций (вместо множества аргументов)
- ✗ Классы с бизнес-логикой (используй regular class)
- ✗ Классы с побочными эффектами