Освой Kotlin играючи

Сайт Александра Климова

Шкодим

/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000

Ключевое слово data

Если у класса указать ключевое слово data, то автоматически будут созданы и переопределены методы toString(), equals(), hashCode(), copy(). Скорее всего вы будете использовать этот вариант для создания полноценного класса-модели с геттерами и сеттерами.


data class Client(val name: String, var postalCode: Int)

В конструкторе класса у параметров следует указывать val или var.

Подобные классы часто используются при работе с JSON.

Классы данных не могут объявляться абстрактными или открытыми, так что класс данных не может использоваться в качестве суперкласса. Однако классы данных могут реализовать интерфейсы, а также могут наследоваться от других классов.

toString()

Если нужно получить информацию о классе, то достаточно вызвать имя переменной класса. Вы получите строку со всеми значениями всех свойств на основе конструктора вместо непонятных символов @Cat5edea как в Java. Такой подход удобен при тестировании и отладке. Сразу понятно, о чём идёт речь.


data class Cat(var name: String, var age: Int)

val cat = Cat("Murzik", 7)
println(cat) // Cat(name=Murzik, age=7)

Можно сразу определить значение по умолчанию у поля класса. При инициализации объекта можно не указывать поле, но оно будет доступно для вычислений.


data class Kitten(
    val name: String,
    var lives: Int = 9
)

val murzik = Kitten("Murzik") // не указываем поле по умолчанию
println(murzik.lives)

val barsik = Kitten("Barsik", 8) // указываем все поля
println(barsik.lives)

equals()

При определении класса данных функция equals() (и оператор ==) по-прежнему возвращает true, если ссылки указывают на один объект. Но она также возвращает true, если объекты имеют одинаковые значения свойств, определённых в конструкторе:


data class Cat(var name: String, var age: Int)
val cat1 = Cat("Murzik", 7)
val cat2 = Cat("Murzik", 7)
println(cat1.equals(cat2)) // true

Если вы переопределяете функцию equals(), также необходимо переопределить функцию hashCode().

Кстати, если вам нужно проверить, что две переменные ссылаются на один объект, то используйте оператор ===. В отличие от оператора ==, поведение оператора === не зависит от функции equals(), которое в разных классах может вести себя по разному. Оператор === всегда ведёт себя одинаково независимо от разновидности класса.

hashCode()

Если два объекта данных считаются равными (имеют одинаковые значения свойств), функция hashCode() возвращает для этих объектов одно и то же значение:


data class Cat(var name: String, var age: Int)

val cat1 = Cat("Murzik", 7)
val cat2 = Cat("Murzik", 7)
val cat3 = Cat("Murzik", 5)

println(cat1.hashCode())
println(cat2.hashCode()) // будет совпадать с cat1
println(cat3.hashCode()) // будет новое значение

copy()

Если вам потребуется создать копию объекта данных, изменяя некоторые из его свойств, но оставить другие свойства в исходном состоянии, воспользуйтесь функцией copy(). Для этого функция вызывается для того объекта, который нужно скопировать, и ей передаются имена всех изменяемых свойств с новыми значениями.


data class Cat(var name: String, var age: Int)

val cat1 = Cat("Barsik", 4)
val cat2 = cat1.copy(age = 7) // у копии объекта меняем только возраст

println(cat1.age)
println(cat2.age)

Фактически мы создаём копию объекта, меняем значение нужного свойства и присваиваем новый объект переменной с новым именем. При этом исходный объект остаётся без изменений.

Деструктурирующее присваивание

При создании data-классов компилятор автоматически добавляет набор функций, с помощью которых можно обратиться к свойствам. Они известны как componentN-функции, где N - это номер свойства. Подсчёт номера идёт по порядку в объявлении класса.


data class Music(val title: String, val author: String)

// Обращаемся к классу
val music = Music("Kalinka", "Abba")
val title = music.component1()
val author = music.component2()
println("$title $author")

Мы могли бы обратиться и привычным способом.


val title = music.title
val author = music.author
println("$title $author")

Деструктуризация позволяет разбить объект на несколько переменных.


data class Cat(var name: String, var age: Int, var city: String)

val cat = Cat("Murzik", 7, "Moscow")
// три переменных в одном месте
val (catName, catAge, place) = cat
println(catName)
println(catAge)
println(place)

Можно пропустить через цикл.


val cats = listOf(Cat("Murzik", 3, "Minks"), Cat("Barsik", 2, "Moscow"))

for((catName, age, city) in cats){
    println("${catName} lives in $city")
}

Можно пропустить какую-то переменную через символ подчёркивания.


val (catName, _, city) = cat

Несколько конструкторов

Добавить второй конструктор к классу можно через ключевое слово constructor.


data class Cat(val name: String) {
    var age = 0;

    constructor(name: String, age: Int) : this(name) {
        this.age = age;
    }
}

Есть другой способ через аннотацию.


data class Cat @JvmOverloads constructor(val name: String, val age: Int? = 0) {

}
Реклама