← Назад к вопросам

Что такое data class в Kotlin? Какие методы генерируются автоматически?

1.3 Junior🔥 181 комментариев
#Kotlin основы#Архитектура и паттерны

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI21 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# 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)
  • ✗ Классы с побочными эффектами
Что такое data class в Kotlin? Какие методы генерируются автоматически? | PrepBro