Классы данных
В Kotlin классы данных в первую очередь используются для хранения данных. Для каждого класса данных компилятор автоматически генерирует
дополнительные функции-члены, которые позволяют выводить экземпляр в читаемом виде, сравнивать экземпляры, копировать их и не только.
Классы данных помечаются ключевым словом data.
data class User(val name: String, val age: Int)
Компилятор автоматически формирует следующие члены данного класса из свойств, объявленных в основном конструкторе:
- пару функций
equals()/hashCode(); - функцию
toString()в форме"User(name=John, age=42)"; - компонентные функции componentN(), соответствующие свойствам в порядке их объявления;
- функцию
copy().
Для обеспечения согласованности и осмысленного поведения сгенерированного кода классы данных должны удовлетворять следующим требованиям:
- Основной конструктор должен иметь как минимум один параметр.
- Все параметры основного конструктора должны быть отмечены как
valилиvar. - Классы данных не могут быть абстрактными,
open,sealedилиinner.
Дополнительно, генерация членов классов данных при наследовании подчиняется следующим правилам:
- Если существуют явные реализации
equals(),hashCode()илиtoString()в теле класса данных илиfinalреализации в суперклассе, то эти функции не генерируются, а используются существующие реализации; - Если суперкласс включает функции
componentN(), которые являются открытыми и возвращают совместимые типы, соответствующие компонентные функции создаются для класса данных и переопределяют функции суперкласса. Если функции суперкласса не могут быть переопределены из-за несовместимости сигнатур или потому что ониfinal, выдаётся сообщение об ошибке; - Предоставление явных реализаций для функций
componentN()иcopy()не допускается.
Классы данных могут расширять другие классы (см. примеры в статье Изолированные классы).
Для того чтобы у сгенерированного в JVM класса был конструктор без параметров, значения всех свойств должны быть заданы по умолчанию (см. Конструкторы):
data class User(val name: String = "", val age: Int = 0)
Свойства, объявленные в теле класса
Компилятор использует только свойства, определенные в основном конструкторе для автоматически созданных функций. Чтобы исключить свойство из автоматически созданной реализации, объявите его в теле класса:
data class Person(val name: String) {
var age: Int = 0
}
В примере ниже только свойство name по умолчанию используется в реализациях функций toString(), equals(), hashCode() и copy(),
и создаётся только одна компонентная функция component1(). Свойство age объявлено в теле класса и исключено.
Поэтому два объекта Person с одинаковым значением name, но разными значениями age считаются равными, так как equals()
проверяет только свойства из основного конструктора.
data class Person(val name: String) {
var age: Int = 0
}
fun main() {
//sampleStart
val person1 = Person("John")
val person2 = Person("John")
person1.age = 10
person2.age = 20
println("person1 == person2: ${person1 == person2}")
// person1 == person2: true
println("person1 with age ${person1.age}: ${person1}")
// person1 with age 10: Person(name=John)
println("person2 with age ${person2.age}: ${person2}")
// person2 with age 20: Person(name=John)
//sampleEnd
}
Копирование
Используйте функцию copy() для копирования объекта, что позволит изменить только некоторые его свойства, оставив остальные неизменными.
Для написанного выше класса User такая реализация будет выглядеть следующим образом:
fun copy(name: String = this.name, age: Int = this.age) = User(name, age)
Это позволяет вам писать:
val jack = User(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)
Функция copy() создаёт поверхностную копию экземпляра. Другими словами, она не копирует компоненты рекурсивно.
В результате ссылки на другие объекты остаются общими.
Например, если свойство хранит изменяемый список, изменения, сделанные через исходное значение, также видны через копию, а изменения, сделанные через копию, видны через исходное значение:
data class Employee(val name: String, val roles: MutableList<String>)
fun main() {
val original = Employee("Jamie", mutableListOf("developer"))
val duplicate = original.copy()
duplicate.roles.add("team lead")
println(original)
// Employee(name=Jamie, roles=[developer, team lead])
println(duplicate)
// Employee(name=Jamie, roles=[developer, team lead])
}
Как видите, изменение свойства duplicate.roles также меняет свойство original.roles, потому что оба свойства ссылаются на один и тот же список.
Классы данных и мульти-декларации
Сгенерированные для классов данных компонентные функции позволяют использовать их в мульти-декларациях.
val jane = User("Jane", 35)
val (name, age) = jane
println("$name, $age years of age")
// Jane, 35 years of age
Стандартные классы данных
Стандартная библиотека предоставляет классы Pair и Triple. Однако, в большинстве случаев, именованные классы данных являются лучшим решением,
потому что делают код более читаемым, предоставляя осмысленные имена для свойств.