Kotlin Advent Calendar 2012 (全部俺)

JavaプログラマのためのKotlin入門

20日目:データクラスと多重宣言

f:id:ngsw_taro:20050408132103j:plain

アドベントカレンダー20日目です。アドベントカレンダーも残すところあとわずかです。言語の文法や機能の紹介は明日まで、という風な予定となっています。今日はデータクラスと多重宣言という機能について見て行きます。

データクラス

単純なプロパティしか持たないようなクラス、いわゆるデータクラスを定義するのにKotlinは持ってこいの文法を採用しています。と言うのは、例えば名前、価格というデータしか持たないクラス Product の定義は次のように簡潔に表現できるからです。

class Product(val name : String, val price : Int)

これは見た目もシンプルですし、記述量も少なくて素晴らしいです。しかしデータクラスたるもの、等価であるか比較したり(関数 equals )、文字列表現を取得したい場合(関数 toString )があります。それらはデータクラスにおいて、たいていの場合同じ内容になります。

例えば上記のクラス Product の場合、name、price これら両方の値が等しい Product インスタンス同士は等しいと見なします。また、関数 toString によって取得される文字列は Product が含むすべてのプロパティの値を上手いこと含んでいれば十分です*1

ここでアノテーション data の登場です!データクラスにアノテーション data を付けると、equals や toString といった関数を自動生成してくれます。

data class Product(val name : String, val price : Int)

fun main(args : Array<String>) {
  println(Product("beer", 200) == Product("beer", 200)) // true
  println(Product("sake", 600)) // Product(name=sake, price=600)
}

さらにアノテーションdata が付いたクラスには、次に説明する関数 component* も 自動生成されます。

多重宣言

多重宣言は、例えば次のようなことができます。

val (foo, bar) = Product("beer", 200)

これは他の言語の多重代入に似ています(Javaにはこのような機能はありませんが)。お察しの通り、変数 foo には "beer" が、変数 bar には 200 が代入されます。

どのような仕組みで多重宣言が行われているのでしょうか。実は先に少し触れた、関数 component* が重要な鍵を握っています。

ひとまずクラス Product からアノテーションdata を外します。すると上記のコードはコンパイルエラーになりますが、Product を次のように書き直すと、再びコードはコンパイルされ、実行結果も期待通りのものとなります。

class Product(val name : String, val price : Int) {
  fun component1() = name
  fun component2() = price
}

コードを見るとわかるとおり、関数 component1 の返り値が変数 foo に、関数 component2 の返り値が変数 bar に代入されます。

component3 以降も定義されすれば期待通りに動いてくれます。これが多重宣言です。

ペアとマップ

クラス Pair は、2つの任意の値をまとめるためのコンテナです*2。次のように、そのインスタンスを得ることができます。

val one = Pair("one", 1)
val two = "two" to 2

上の例はコンストラクタからインスタンスを生成しています。下の例は関数から Pair インスタンスを生成していますが、関数名からわかるとおり、この方法はキーバリュー値として Pair を用いたい場合に使う方がよさそうです。

クラス Pair ではメンバ関数として component1 と component2 を定義しています。つまり、多重宣言が可能です。

val (text, number) = Pair("three", 3)

ところで、Kotlinの標準ライブラリのパッケージ kotlin では、jet.Map の拡張関数として iterator を提供しています。このイテレータの要素の型は Map.Entry です。そして、Entry の拡張関数として component1 と component2 が定義されています。

つまりマップのイテレート + 多重宣言で次のようなスッキリとしたコードを書くことが可能になります。

val map = hashMap(
  "one"   to 1,
  "two"   to 2,
  "three" to 3
)
  
for((key, value) in map) {
  println("$key -> $value")
}

まとめと次回予告

今日はデータクラスと多重宣言という面白い機能についてお話ししました。アノテーション data をクラスに付けることによって、データクラスのための関数が自動生成されます。また関数 component* を適切に定義すると、そのクラスのインスタンスから多重宣言により複数の変数に一度に値を代入することができます。

明日のトピックは...明日になってからのお楽しみ!次回もサービスサービスぅ!!

日記

今朝、めちゃくちゃ寒い(´・ω・`)

*1:関数 toString によって得られる文字列は、デバッグやログ出力のみに使われるべきです。少なくとも、その文字列に依存した挙動を行う処理は避けるべきです。

*2:余談ですが、Kotlinの旧バージョンでは、ペアやトリプルなどはタプルという機能で表現されていました。