Найти в Дзене

Классы данных в Kotlin

Оглавление

1. Обзор

В языке Kotlin введено понятие классов данных (Data Classes), основная цель которых - просто хранить данные без необходимости вводить шаблонный код, необходимый в Java для создания POJO (Plain Old Java Object). Проще говоря, решение Kotlin позволяет избежать написания методов-геттеров, методов-сеттеров, а также методов equals и hashCode, что делает классы моделей более чистыми и читаемыми.

В этой краткой статье мы рассмотрим классы данных в Kotlin и сравним их с их аналогами в Java.

2. Data-классы в Java

Если бы мы хотели создать запись задачи на Java, нам пришлось бы написать много шаблонного кода:

public class Task {
private int id;
private String description;
private int priority;

public Task(int id, String description, int priority) {
this.id = id;
this.description = description;
this.priority = priority;
}

public void setId(int id) {
this.id = id;
}

public int getId() {
return id;
}

public void setDescription(String description) {
this.description = description;
}

public String getDescription() {
return description;
}

public void setPriority(int priority) {
this.priority = priority;
}

public float getPriority() {
return priority;
}

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = Integer.hashCode(this.id) * prime;
result = prime * result + Integer.hashCode(this.priority);
result = prime * result + ((this.description == null) ? 0 : this.description.hashCode());
return result;
}

@Override
public boolean equals(Object var1) {
if (this != var1) {
if (var1 instanceof Task) {
Task var2 = (Task) var1;
if (this.id == var2.id
&& Intrinsics.areEqual(this.description, var2.description)
&& this.priority == var2.priority) {
return true;
}
}
return false;
} else {
return true;
}
}

@Override
public String toString() {
return "Task [id=" + id + ", description=" + description + ", priority=" + priority + "]";
}
}

69 строк кода! Это много для хранения всего лишь трех полей в простом классе.

3. Data-классы в Kotlin

Теперь давайте переопределим тот же класс Task с той же функциональностью, но в виде data-класса в Kotlin:

data class Task(
var id: Int,
var description: String,
var priority: Int
)

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

3.1 Применение

Мы создаем экземпляр data-класса таким же образом, как и у других классов:

val task= Task(1001, "Replace Fuel Tank Filler Caps", 5)

Теперь свойства и функции доступны:

println(task.id) // 1001
println(task.description) // Replace Fuel Tank Filler Caps
println(task.priority) // 5

task.priority = 4

println(task.toString())

3.2 Функция копирования

Функция copy() создана для нас в случае необходимости создания копии объекта, изменяя некоторые из его свойств, но оставляя остальные без изменений:

val copyTask = task.copy(priority = 4)
println(copyTask.toString())

Java не предоставляет явного и нативного способа для копирования/клонирования объектов. Мы могли бы использовать интерфейс Clonable, SerializationUtils.clone() или конструктор клонирования.

3.3 Деструктуризация деклараций

Декларации деструктуризации позволяют рассматривать свойства объектов как отдельные значения. Для каждого свойства в нашем data-классе генерируется функция componentN():

task.component1()
task.component2()
task.component3()

Мы также можем создавать несколько переменных из объекта или непосредственно из функции - важно помнить об использовании скобок:

val(id, description, priority) = task

fun getTask() = movie
val(idf, descriptionf, priorityf) = getTask()

3.4 Ограничения класса данных

Для создания data-класса необходимо учесть следующие условия:

  • Объявление класса должно начинаться с ключевого слова data.
  • Класс должен иметь как минимум один параметр конструктора.
  • Все параметры конструктора должны быть объявлены как val или var.
  • Data-класс не может быть открытым (open), запечатанным (sealed), абстрактным (abstract) или внутренним (inner).
  • Родительский класс data-класса недоступен компилятору для использования в определении автоматически сгенерированной функции copy().

Если сгенерированный класс должен иметь конструктор без параметров, то для всех свойств необходимо указать значения по умолчанию:

data class Task(var id: Int = 1000, var description: String = "", var priority: Int= 0)

4. Совместимость с записями (Records) в Java

Начиная с Kotlin 1.5, мы можем скомпилировать data-классы Kotlin как записи (records) в Java 14+. Для этого достаточно аннотировать data-класс аннотацией @JvmRecord:

@JvmRecord
data class Person(val firstName: String, val lastName: String)

Для компиляции этого кода мы можем использовать компилятор Kotlin (kotlinc):

>> kotlinc -jvm-target 15 -Xjvm-enable-preview Person.kt

Если мы ориентируемся на Java 15 или более ранние версии, мы должны включить предварительные версии JVM с помощью флага -Xjvm-enable-preview. Однако начиная с Java 16, записи (records) являются стабильными возможностями Java. Поэтому, если мы ориентируемся на Java 16 или более новые версии, нам не нужно включать предварительные возможности:

>> kotlinc -jvm-target 16 Person.kt

Теперь, если мы взглянем на сгенерированный байт-код, мы увидим, что класс Person расширяет класс java.lang.Record:

>> javap -c -p com.baeldung.dataclass.Person
Compiled from "Person.kt"
public final class com.baeldung.dataclass.Person extends java.lang.Record {
// omitted
}

Поскольку записи (records) в Java являются неизменяемыми, мы не можем использовать объявления с var для data-классов, аннотированных как @JvmRecord:

@JvmRecord // won't compile
data class Person(val firstName: String, var lastName: String)

Здесь компилятор выдаст следующее сообщение об ошибке:

Constructor parameter of @JvmRecord class should be a val

Более того, этот тип data-класса не может расширять другие классы, поскольку он уже расширяет суперкласс Record.

5. Заключение

Мы рассмотрели data-классы в Kotlin, их использование и требования, уменьшенное количество шаблонного кода, а также сравнения с аналогичным кодом на Java.

Оригинал статьи: https://www.baeldung.com/kotlin/data-classes