Урок 22: Data class (дата классы) copy, toString, equals, hashCode в Kotlin

Data class дата классы copy toString equals hashCode 1 - Android [Kotlin] для начинающих

Data классы

Я уже рассказывал вам про классы и ООП, начиная с 11 урока. И вы понимаете насколько это мощный инструмент, позволяющий писать код приближенный к реальности.

Классические классы используются для организации кода, реализации ООП подходов. Создатели языка Kotlin сделали свой тип классов, специально для хранения данных –Data классы. Строго говоря, это обычные классы у которых есть дополнительные возможности. Со временем вы полностью почувствуете разницу, пока лишь сосредоточусь на основных отличиях, которые легко понять и запомнить.

Что значит для хранения данных? Все то, что мы делали раньше, когда создавали и описывали сущности телефонных справочников, космических кораблей, словарей для переводчиков и так далее.

Дополнительные возможности Data классов

Какими дополнительными возможностями обладают Data классы в сравнении с обычными? Если совсем коротко, то для объектов этих классов можно вызвать новые методы. Такие как toString(), equals(), hashCode() и copy().

  • equals() – производит сравнение двух объектов;
  • hashCode() – позволяет получить хэш-код объекта (уникальный целочисленный номер объекта);
  • toString() – возвращает представление объекта в виде понятной строки (именно понятной, об этом будет подробнее ниже);
  • copy() – копирует данные объекта в другой объект.

Метод toString()

Понятнее всего будет показать ценность этого метода, сперва обозначив проблему. При разработке телеграм бота для изучения иностранных слов мы выделили сущность, которая должна хранить слово на одном языке и его перевод. Создадим классический класс Word с полями text и translate. Тут же создадим объект “слова” и попытаемся распечатать его в консоль.

class Word(
	val text: String,
	val translate: String,
)

fun main(){
	val word = Word("Red", "Красный")
	println(word)
}

Получаем непонятный набор символов additional.Word@452b3a41. Так выглядит строковое представление объекта по умолчанию. Но мы хотим увидеть в строке понятные данные объекта. Как решить эту проблему?

Вариант 1. Используя классический класс. Может быть к объекту добавить принудительное приведение к строке? Нет, *println*(word.toString()) не сработает. Так как метод println() под капотом использует тот же toString() и результат будет абсолютно таким же. Окей. Но можно переопределить стандартный toString(), добавив в него нужное нам поведение.

Для этого в теле класса сочетанием клавиш cmd+N сгенерируем toString(). В следующем окне IDEA предложит выбрать поля объекта, для которых будет создано новое текстовое представление. Оставляем оба.

Получаем переопределенную функцию toString(). По кружочку со стрелкой можно переместиться к исходной функции, которую переопределили. Теперь метод возвращает красивую строку с полями объекта. Запускаем и видим в консоли ожидаемый результат Word(text=’Red’, translate=’Красный’).

class Word(
	val text: String,
	val translate: String,
) {
	override fun toString(): String {
		return "Word(text='$text', translate='$translate')"
	}
}

Вариант 2. Добавить ключевое слово data к классу и просто вызвать печать объекта. Тело класса уже не нужно. Запускаем и получаем точно такой же результат.

fun main(){
	val word = Word("Red", "Красный")
	println(word)
}

data class Word(
	val text: String,
	val translate: String,
)fun main(){
	val word = Word("Red", "Красный")
	println(word)
}

data class Word(
	val text: String,
	val translate: String,
)

При необходимости здесь тоже можно переопределять toString(). Если вдруг нас не будет устраивать реализация по умолчанию.

Метод equals()

Эта функция занимается сравнением и аналогична оператору “двойное равно” (==) и по аналогии с тустринг ее можно сгенерировать с помощью среды разработки. Можете самостоятельно посмотреть ее реализацию.

Добавим несколько объектов для сравнения. Если попробовать сравнить пару через equals(), среда разработки предложит заменить функцию на оператор. Так и сделаем.

fun main(){
	val word1 = Word("Red", "Красный")
	val word2 = Word("Red", "Красный")
	val word3 = Word("White", "Белый")
	println(word1 == word2)
	println(word2 == word3)
}

При сравнении обычных классов оба вывода будет false. Потому, что сравниваются ссылки на объекты в памяти, а не значения. Но Data класс позволяет сравнить именно проинициализированные данные. Поэтому видим закономерный результат true и false.

И еще, помните про оператор ссылочного сравнения? Тройное равно (===). Он никак не относится к equals() и ведет себя одинаково в не зависимости от типа класса.

Еще одна интересная функция из арсенала Data классов – copy(). Во-первых – она умеет копировать объекты целиком.

fun main(){
	val word1 = Word("Red", "Красный")
	val word2 = Word("Red", "Красный")
	val word3 = Word("White", "Белый")
//	println(word1 == word2)
//	println(word2 == word3)

	val word4 = word3.copy()
	println(word4)
}

Во-вторых, и эта возможность довольно часто используется, метод умеет копировать объекты с выборочным изменением полей.

fun main(){
	val word1 = Word("Red", "Красный")
	val word2 = Word("Red", "Красный")
	val word3 = Word("White", "Белый")
//	println(word1 == word2)
//	println(word2 == word3)

	val word4 = word3.copy(translate = "Правильный перевод: Белый")
	println(word3)
	println(word4)
}

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

Метод hashCode()

Этот метод есть в обоих разновидностях класса, но в Data классе он переопределен и имеет другую реализацию. Метод возвращает уникальный код объекта. Он также может использоваться для сравнения, как и equals(), но работает быстрее. Потому, что два числа сравнивать быстрее, чем все свойства и значения объекта. Если два объекта Data класса имеют одинаковые свойства, то и хэш-код у них будет одинаковый.

println(word1.hashCode())
println(word2.hashCode())

У одинаковых data-классов одинаковый хэшкод, потому что для них генерируется hashcode() функция основанная на полях внутри классов.

public int hashCode() {
      String var10000 = this.original;
      int var1 = (var10000 != null ? var10000.hashCode() : 0) * 31;
      String var10001 = this.translate;
      return var1 + (var10001 != null ? var10001.hashCode() : 0);
   }

Обычные же, не дата классы использую hashcode() базового класса Object, реализация которого игнорирует поля класса, и для упрощения можно сказать, что hashcode генерируется случайным образом для каждого объекта (зависит от реализации JVM).

public class Object {
	//...
	@IntrinsicCandidate
	public native int hashCode();
	//...
}

Подробнее мы не будем закапываться на этом уровне изучения языка. Если вам интересен более глубокий разбор исходников и поиск причинно-следственных связей – ссылку на материал оставлю в описании.

И это конец. Data классы, их возможности и другие продвинутые темы (которые не освещены на канале) мы используем в курсовой работе по написанию своего телеграм бота на Kotlin.

Для тех, кто собрался стать Android-разработчиком

Пошаговаясхема
Пошаговая
схема

Описание процесса обучения от основ Kotlin до Android-разработчика

Бесплатныеуроки
Бесплатные
уроки

Авторский бесплатный курс по основам языка программирования Kotlin

Обучающийбот
Обучающий
бот

Тренажер и самоучитель по Котлин – бесплатные тесты и практика

Поделиться уроком

Ответить

Ваш адрес email не будет опубликован. Обязательные поля помечены *