🐥
【Kotlin】data class と class
はじめに
Kotlin のdata class
とclass
の理解の違いで予期せぬバグに遭遇しました。
勉強になったので動作を確認しながら学んだ内容を残します!
「data class」と「class」比較表
data class | class | |
---|---|---|
equals() | 同じ値(プロパティ)を持ったインスタンスかどうかを比較する | 同じインスタンスであるかどうかを比較する |
hashCode() | インスタンスの値(プロパティ)によってコードが出力される | インスタンスごとに固有のコードが出力される |
toString() | 「型(プロパティ=値)」の形式で返す | 「クラス@xxxxx」の形式?で返す |
componentN() | インスタンスのプロパティに簡単にアクセスが可能 | なし |
copy() | 同じプロパティを持つ(一部変更も可能)別のインスタンスを生成する | なし |
以下でそれぞれ動作を確認していく。
※ 実行環境: Kotlin Playground
equals()
class: 同じインスタンスであればtrue
そうでなければfalse
data class: 同じ値(プロパティ)を持ったインスタンスであればtrue
そうでなければfalse
紛らわしいぜ...
コード
class Person(var name: String, var age: Int)
data class DataPerson(var name: String, var age: Int)
fun main() {
val person1 = Person("タナカ", 20)
val person2 = Person("タナカ", 20)
val dataPerson1 = DataPerson("タナカ", 20)
val dataPerson2 = DataPerson("タナカ", 20)
println("classの同じインスタンス: " + person1.equals(person1))
println("classの別インスタンス: " + person1.equals(person2))
println("data classの同じインスタンス: " + dataPerson1.equals(dataPerson1))
println("data classの別インスタンス: " + dataPerson1.equals(dataPerson2))
}
出力結果
classの同じインスタンス: true
classの別インスタンス: false
data classの同じインスタンス: true
data classの別インスタンス: true
equals() 参考資料
- https://kotlinlang.org/docs/equality.html
- https://www.youtube.com/watch?v=5Om9z5i8Pt0&list=PL0BiAlg8j4Zv1nfx4t1z5tshjScwtrNCJ&index=4
- https://techacademy.jp/magazine/32629
hashCode()
class: インスタンスが異なれば、値(プロパティ)は同じでも異なるハッシュコードが出力される
data class: 同じ値(プロパティ)を持ったインスタンスであれば、異なるインスタンスであっても同じハッシュコードが出力される
コード
class Person(var name: String, var age: Int)
data class DataPerson(var name: String, var age: Int)
fun main() {
val person1 = Person("タナカ", 20)
val person2 = Person("タナカ", 20)
val person3 = Person("スズキ", 21)
val dataPerson1 = DataPerson("タナカ", 20)
val dataPerson2 = DataPerson("タナカ", 20)
val dataPerson3 = DataPerson("スズキ", 21)
println("class person1: " + person1.hashCode())
println("class person2: " + person2.hashCode())
println("class person3: " + person3.hashCode())
println("data class dataPerson1: " + dataPerson1.hashCode())
println("data class dataPerson2: " + dataPerson2.hashCode())
println("data class dataPerson3: " + dataPerson3.hashCode())
}
出力結果
class person1: 1414644648
class person2: 1995265320
class person3: 746292446
data class dataPerson1: 384151028
data class dataPerson2: 384151028
data class dataPerson3: 383956969
toString()
コード
class Person(var name: String, var age: Int)
data class DataPerson(var name: String, var age: Int)
fun main() {
val person1 = Person("タナカ", 20)
val dataPerson1 = DataPerson("タナカ", 20)
println("class person1: " + person1.toString())
println("data class dataPerson1: " + dataPerson1.toString())
}
出力結果
class person1: Person@5451c3a8
data class dataPerson1: DataPerson(name=タナカ, age=20)
componentN()
コンストラクタ引数N番目のプロパティにアクセスできる。
コード
data class DataPerson(var name: String, var age: Int, var from: String)
fun main() {
val dataPerson1 = DataPerson("タナカ", 20, "日本")
val dataPerson2 = DataPerson("スズキ", 25, "アメリカ")
println("dataPerson1: " + "①" + dataPerson1.component1() + " ②" + dataPerson1.component2() + " ③" + dataPerson1.component3())
println("dataPerson2: " + "①" + dataPerson2.component1() + " ②" + dataPerson2.component2() + " ③" + dataPerson2.component3())
}
出力結果
dataPerson1: ①タナカ ②20 ③日本
dataPerson2: ①スズキ ②25 ③アメリカ
分解宣言
こんなこともできるらしい。
(JavaScript の分割代入みたい。)
コード
data class DataPerson(var name: String, var age: Int, var from: String)
fun main() {
val dataPerson1 = DataPerson("タナカ", 20, "日本")
val dataPerson2 = DataPerson("スズキ", 25, "アメリカ")
val (name, age, from) = dataPerson1
// 変数名は何でもOK
val (a, b, c) = dataPerson2
println("dataPerson1: " + "①" + name + " ②" + age + " ③" + from)
println("dataPerson2: " + "①" + a + " ②" + b + " ③" + c)
}
出力結果
dataPerson1: ①タナカ ②20 ③日本
dataPerson2: ①スズキ ②25 ③アメリカ
この分解宣言は以下のコードにコンパイルされているようである。
val name = dataPerson1.dataPerson1()
val age = dataPerson1.component2()
val from = dataPerson1.dataPerson3()
copy()
同じプロパティを持つ別のインスタンスを生成することができる。
(または一部の異なるプロパティを持つ別のインスタンスを生成)
コード
data class DataPerson(var name: String, var age: Int, var from: String)
fun main() {
val original = DataPerson("タナカ", 20, "日本")
val copy = original.copy()
// $ は 「+ original.toString()」 と同じ
println("original: $original")
println("copy : $copy")
}
出力結果
original: DataPerson(name=タナカ, age=20, from=日本)
copy : DataPerson(name=タナカ, age=20, from=日本)
一部のプロパティを変えてコピー
コード
data class DataPerson(var name: String, var age: Int, var from: String)
fun main() {
val original = DataPerson("タナカ", 20, "日本")
val copy = original.copy(name = "スズキ", from = "アメリカ")
println("original: $original")
println("copy : $copy")
}
出力結果
original: DataPerson(name=タナカ, age=20, from=日本)
copy : DataPerson(name=スズキ, age=20, from=アメリカ)
※ 代入するだけではコピーされない
コード
data class DataPerson(var name: String, var age: Int, var from: String)
fun main() {
val original = DataPerson("タナカ", 20, "日本")
val dainyu = original
val copy = original.copy()
original.name = "スズキ"
// original の name に代入しただけなのに、dainyu の name も"スズキ"に変わってしまっている!!
println("original: $original")
println("dainyu : $dainyu")
println("copy : $copy")
}
出力結果
original: DataPerson(name=スズキ, age=20, from=日本)
dainyu : DataPerson(name=スズキ, age=20, from=日本)
copy : DataPerson(name=タナカ, age=20, from=日本)
これは代入するだけでは、同じオブジェクトを参照してしまうためである。
つまり、今回で言うとoriginal
もdainyu
も同じものを見にいっている。
最後に
記事投稿エラい。
疲れました..
大変参考にさせていただきました!ありがとうございました!
- https://dogwood008.github.io/kotlin-web-site-ja/docs/reference/data-classes.html
- https://dogwood008.github.io/kotlin-web-site-ja/docs/reference/multi-declarations.html
- https://pouhon.net/kotlin-dataclass/3375/
- https://www.sejuku.net/blog/103617
- https://qiita.com/kaleidot725/items/82510dde1082acddf8be
- https://techacademy.jp/magazine/19797
Discussion