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