Классы данных
Нередко мы создаём классы, единственным назначением которых является хранение данных.
Функционал и некоторые служебные функции таких классов зависят от самих данных, которые в них хранятся.
В 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()
. Даже если два объекта класса Person
будут иметь разные значения свойств age
,
они будут считаться равными.
val person1 = Person("John")
val person2 = Person("John")
person1.age = 10
person2.age = 20
Копирование
Используйте функцию 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)
Классы данных и мульти-декларации
Сгенерированные для классов данных компонентные функции позволяют использовать их в мульти-декларациях.
val jane = User("Jane", 35)
val (name, age) = jane
println("$name, $age years of age") // выводит "Jane, 35 years of age"
Стандартные классы данных
Стандартная библиотека предоставляет классы Pair
и Triple
. Однако, в большинстве случаев, именованные классы данных являются лучшим решением,
потому что делают код более читаемым, избегая малосодержательные имена для свойств.