データクラス
Kotlinのデータクラスは、主にデータを保持するために使用されます。各データクラスに対して、コンパイラはインスタンスを読み取り可能な形式で出力したり、インスタンスを比較したり、コピーしたりできる追加のメンバー関数を自動的に生成します。データクラスはdataキーワードでマークされます:
data class User(val name: String, val age: Int)コンパイラは、プライマリコンストラクタで宣言されたすべてのプロパティから、以下のメンバーを自動的に導出します:
equals()/hashCode()ペア。"User(name=John, age=42)"形式のtoString()。- 宣言順に対応するプロパティの
componentN()関数。 copy()関数 (以下参照)。
生成されたコードの一貫性と意味のある動作を保証するために、データクラスは以下の要件を満たす必要があります:
- プライマリコンストラクタには少なくとも1つのパラメータが必要です。
- すべてのプライマリコンストラクタパラメータは
valまたはvarでマークされている必要があります。 - データクラスは
abstract、open、sealed、innerであってはなりません。
さらに、データクラスメンバーの生成は、メンバーの継承に関して以下の規則に従います:
- データクラスの本体に
equals()、hashCode()、toString()の明示的な実装がある場合、またはスーパークラスにfinalな実装がある場合、これらの関数は生成されず、既存の実装が使用されます。 - スーパークラスが
openで互換性のある型を返すcomponentN()関数を持つ場合、対応する関数がデータクラスに生成され、スーパークラスのものをオーバーライドします。スーパークラスの関数が互換性のないシグネチャのため、またはfinalであるためにオーバーライドできない場合、エラーが報告されます。 componentN()関数とcopy()関数に明示的な実装を提供することは許可されていません。
データクラスは他のクラスを拡張できます (密封クラスの例を参照)。
On the JVM, 生成されたクラスが引数なしのコンストラクタを持つ必要がある場合、プロパティにデフォルト値を指定する必要があります (Constructorsを参照):
kotlindata class User(val name: String = "", val age: Int = 0)
クラス本体で宣言されたプロパティ
コンパイラは、自動生成される関数に対してプライマリコンストラクタ内で定義されたプロパティのみを使用します。生成される実装からプロパティを除外するには、クラス本体内で宣言します:
data class Person(val name: String) {
var age: Int = 0
}以下の例では、toString()、equals()、hashCode()、およびcopy()の実装ではデフォルトでnameプロパティのみが使用され、component1()というコンポーネント関数は1つだけです。ageプロパティはクラス本体内で宣言されており、除外されます。 したがって、equals()はプライマリコンストラクタのプロパティのみを評価するため、同じnameで異なるage値を持つ2つのPersonオブジェクトは等しいとみなされます:
data class Person(val name: String) {
var age: Int = 0
}
fun main() {
val person1 = Person("John")
val person2 = Person("John")
person1.age = 10
person2.age = 20
println("person1 == person2: ${person1 == person2}")
// person1 == person2: true
println("person1 with age ${person1.age}: ${person1}")
// person1 with age 10: Person(name=John)
println("person2 with age ${person2.age}: ${person2}")
// person2 with age 20: Person(name=John)
}コピー
copy()関数を使用してオブジェクトをコピーすると、一部のプロパティを変更しつつ、残りを変更せずに維持できます。上記のUserクラスに対するこの関数の実装は次のようになります:
fun copy(name: String = this.name, age: Int = this.age) = User(name, age)その後、次のように記述できます:
val jack = User(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)copy()関数はインスタンスの_シャローコピー_を作成します。言い換えれば、コンポーネントを再帰的にコピーすることはありません。その結果、他のオブジェクトへの参照は共有されます。
例えば、プロパティが可変リストを保持している場合、「オリジナル」の値を通じて行われた変更はコピーを通じても可視であり、コピーを通じて行われた変更はオリジナルを通じても可視となります:
data class Employee(val name: String, val roles: MutableList<String>)
fun main() {
val original = Employee("Jamie", mutableListOf("developer"))
val duplicate = original.copy()
duplicate.roles.add("team lead")
println(original)
// Employee(name=Jamie, roles=[developer, team lead])
println(duplicate)
// Employee(name=Jamie, roles=[developer, team lead])
}ご覧のとおり、duplicate.rolesプロパティを変更するとoriginal.rolesプロパティも変更されます。これは、両方のプロパティが同じリスト参照を共有しているためです。
データクラスと分解宣言
データクラスのために生成されるコンポーネント関数は、それらを分解宣言で使用することを可能にします:
val jane = User("Jane", 35)
val (name, age) = jane
println("$name, $age years of age")
// Jane, 35 years of age標準データクラス
標準ライブラリはPairクラスとTripleクラスを提供します。しかし、ほとんどの場合、名前付きデータクラスはプロパティに意味のある名前を提供することでコードを読みやすくするため、より良い設計選択肢となります。
