データクラスとタプル (Android Kotlin)

f:id:araemonz:20190202150705j:plain
データクラスとタプル

データクラス

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

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

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

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

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

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

分解宣言

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

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

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

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

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はこんな感じ。

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

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

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

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

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 (クオドルプル)を作ってみよう。

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が出来上がった。

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

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型になっているので注意だ。

Pair

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

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

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

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

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

これはMap型のオブジェクトを作るときにやっていることだ。

だから当然、Map型もPair型と同じようにfor文でkeyとvalueをマッピングできるわけだ。

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

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

参考