Kotlinのデータクラスとタプルを理解する【Androidアプリ開発】

Kotlinのデータクラスとタプルを理解する【Androidアプリ開発】
Kotlinのデータクラスとタプルを理解する【Androidアプリ開発】

Kotlinのデータクラスとタプルが面白かったので解説していく。一見、データクラスとタプルは何も関係ないように思えるかもしれないが、実はかなり近い仲間であると言って良い。普段なにげなく使っている関数も、どうやってできているのか中身の構造を知るとプログラミング言語自体が面白く見えてくる。プログラミング初心者から一歩先へ進みたいかたにおすすめな内容なので、ぜひ参考にしてみてほしい。

データクラス

構造体のデータ管理するためにclassを使いたい場合、次のようにプロパティだけもたせたクラスを作ることがあるだろう。

kotlin
class Kami(val level:Int, val kanji: String, val kana:String)

このクラスは、修飾子dataをつけることで大変良いことが起こる。

kotlin
data class Kami(
    val level:Int = 20,
    val kanji: String = "天照大神",
    val kana:String = "アマテラスオオミカミ"
)

自動で次のメソッドを実装してくれるのだ。

  • equals()
  • hashCode()
  • toString()
  • copy()
  • componentN()

分解宣言

たとえば、componentN()のメソッドが実装されたおかげで、次のように一度に変数をマッピングして取り出せる。

kotlin
val defaultKami = Kami()
val (level, name, kana) = defaultKami
println("$level, $name, $kana")

これを 分解宣言(destructuring declaration) と呼ぶ。

もし、修飾子dataをつけなかったら、自前でcomponentNを実装しなければならない。

kotlin
class Kami(
    val level:Int = 20,
    val kanji: String = "天照大神",
    val kana:String = "アマテラスオオミカミ"
) {
    operator fun component1(): Int = level
    operator fun component2(): String = kanji
    operator fun component3(): String = kana
}

Triple

ところでKotlinには、SwiftやPythonで使える可変可能なタプルが存在しない。しかし内容が限定されたPairやTripleといったクラスが用意されている。たとえばTripleはこんな感じ。

kotlin
val (id, name, furigana) = Triple(5, "汐華初流乃", "しおばなはるの")
println("$id, $name, $furigana")

データクラスで行った分解宣言と似ていないだろうか?

それもそのはず、PairやTripleもデータクラスなのだ!

Tripleの定義をみると次のようになっている。

kotlin
public data class Triple<out A, out B, out C>(
    public val first: A,
    public val second: B,
    public val third: C
) : Serializable {
    public override fun toString(): String = "($first, $second, $third)"
}

public fun <T> Triple<T, T, T>.toList(): List<T> = listOf(first, second, third)
修飾子out は、ジェネリック関数の型投影で共変を意味する。これによって、さまざな任意の型の値を、引数へ渡せるようになるのだ。

Quadruple

Kotlin標準関数であるTripleの定義を真似て、Quadruple(クオドルプル)を作ってみよう。

kotlin
internal typealias Serializable = java.io.Serializable

public data class Quadruple<out A, out B, out C, out D>(
    public val first: A,
    public val second: B,
    public val third: C,
    public val quad: D
) : Serializable {
    public override fun toString(): String = "Quadruple data is ($first, $second, $third, $quad)"
}

public fun <T> Quadruple<T, T, T, T>.toList(): List<T> = listOf(first, second, third, quad)

Tripleの定義にプロパティquad を追加しただけで、とても簡単にQuadrupleができた。

それでは、実行してみよう。

kotlin
fun main() {
    val hero = Quadruple(5, "汐華初流乃", "しおばなはるの", "ゴールド・エクスペリエンス")
    println(hero)

    val list = hero.toList()
    println(list)
}

実行してみると次のように出力された。

Quadruple data is (5, 汐華初流乃, しおばなはるの, ゴールド・エクスペリエンス)
[5, 汐華初流乃, しおばなはるの, ゴールド・エクスペリエンス]

1行目は、toString()をオーバーライドしているので期待通りの結果だ。

2行目は、listの中身が表示されている。今回、Quadrupleの値がInt型とString型の複数を持っているため、toListメソッドで返されるListはList<Any>型になっているので注意だ。

Pair

最後に、Pair型も使ってみよう。List<Pair>型のリストをfor文で回すと、次のように値を取りだせる。すでに分解宣言を知っているので、まったく不思議ではない。

kotlin
val fruits = listOf<Pair<String, Int>>(
    Pair("バナナ", 100),
    Pair("りんご", 198)
)

for ((name, cost) in fruits) {
    println("果物: $name, 値段: $cost")
}

ところで、Pair型のオブジェクトを作るときは次のように書くことができる。

kotlin
val pair:Pair<String, Int> = "A" to 1

これはMap型のオブジェクトを作るときにやっていることだ。だから当然、Map型もPair型と同じようにfor文でkeyとvalueをマッピングできるわけだ。

kotlin
val capitals = mapOf<String, String>(
    "日本" to "東京", 
    "アメリカ合衆国" to "ワシントンD.C."
)

for ((k, v) in capitals) {
    println("国: $k, 首都: $v")
}

参考

Basic Syntax - Kotlin Programming Language Idioms - Kotlin Programming Language

▼ こんな記事も書いてます。

関連記事

最後までご覧いただきありがとうございます!

▼ 記事に関するご質問やお仕事のご相談は以下よりお願いいたします。
お問い合わせフォーム

関連記事