1. 概要

The Kotlin language has introduced the concept of Data Classes, whose main purpose is to simply hold data without the boilerplate code needed in Java to create a POJO. Simply put, Kotlin’s solution enables us to avoid writing getters, setters, equals, and hashCode methods, so it makes the model classes cleaner and more readable.

In this quick article, we’ll have a look at Data Classes in Kotlin and compare them with their Java counterparts.

2. Kotlinのセットアップ

To get started setting up the Kotlin project, check our Introduction to the Kotlin Language tutorial.

3. Javaのデータクラス

If we wanted to create a Task entry in Java, we’d need to write a lot of boilerplate code:

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 lines of code! 単純なクラスに3つのフィールドだけを格納するのは大変です。

4. Kotlin Data Classes

Now, let’s redefine the same Task class, with the same functionality, as a Kotlin Data Class:

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

ご覧のとおり、これは非常に簡単でクリーンです。 Kotlin generates the basic functions that must be overloaded for a good model class, giving us good toString(), hashCode(), and equals() functions. Kotlin also provides some extra functionality in the form of a copy() function, and various componentN() functions, which are important for variable destructuring.

4.1. 使用法

We instantiate a data class the same way as other classes:

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())

4.2. コピー機能

The copy() function is created for us in case we need to copy an object, altering some of its properties but keeping the rest unchanged:

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

Javaは、オブジェクトをコピー/複製するための明確でネイティブな方法を提供していません。 We could use the Clonable interface, SerializationUtils.clone(), or a cloning constructor.

4.3. 破壊宣言

Destructuring declarations allow us to treat objects’ properties as individual values. For each property in our data class, a componentN() function is generated:

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

オブジェクトから、または関数から直接複数の変数を作成することもできます。角かっこを使用することを覚えておくことが重要です。

val(id, description, priority) = task

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

4.4. Data Class Restrictions

In order to create a data class, we have to consider the following conditions:

  • The class declaration must start with data
  • It must have at least one constructor parameter
  • All constructor parameters must be vals or vars
  • A data class can’t be open, sealed, abstract, or an inner classes
  • The parent of a data class isn’t available to the compiler to use in defining the generated copy() function

生成されたクラスにパラメーターなしのコンストラクターが必要な場合は、すべてのプロパティのデフォルト値を指定する必要があります。

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

5. Javaレコードの互換性

Kotlin 1.5 から、KotlinデータクラスをJava14+レコードとしてコンパイルできるようになりました。 これを実現するには、データクラス@JvmRecordアノテーションを付けるだけです。

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

これをコンパイルするために、kotlincを使用できます。

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

If we’re targeting Java 15 or older versions, we have to enable the preview JVM versions via the -Xjvm-enable-preview flag. ただし、Java 16の時点では、レコードは安定したJava機能です。 したがって、Java 16以降のバージョンを対象としている場合は、プレビュー機能を有効にする必要はありません。

>> kotlinc -jvm-target 16 Person.kt

Now, if we take a peek at the generated bytecode, we’ll see that the Person class extends the java.lang.Record class:

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

Javaレコードは不変であるため、@JvmRecordで注釈が付けられたデータクラスにvar宣言を使用することはできません。

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

ここで、コンパイラは次のエラーメッセージで失敗します。

Constructor parameter of @JvmRecord class should be a val

Moreover, this type of data class can’t extend other classes, as it’s already extending the Record superclass.

6. 結論

We’ve seen Data Classes in Kotlin, their usage and requirements, the reduced amount of boilerplate code required, and comparisons with the same code in Java.

To learn more about Kotlin, check articles such as Kotlin Java Interoperability and the already mentioned Introduction to the Kotlin Language.

これらの例の完全な実装は、GitHubにあります。