Kotlin Advent Calendar 2012 (全部俺)

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

2日目:Kotlin名所見学ツアー

f:id:ngsw_taro:20121129064944j:plain

アドベントカレンダー2日目。今日はKotlin言語を俯瞰して、面白い機能をつまみ食いしていきます。Javaにはない発想がいくつか見られますが、ここではあえて詳細な説明はしません。Kotlinの雰囲気をつかんで、わくわく感を持って明日以降の記事をご覧いただければと思います。

forループ

いきなり退屈そうな話題だとがっかりしないでください。あとで興味深い機能がたくさん登場します。このツアーはまずはここからスタートするというだけです。

for(i in integers) {
    println("count : $i")
}

Kotlinでは、Javaの拡張for文に似ているこの形でしかfor文を使用することができません。ループカウンタをforの括弧内でいじくりまわすということはできないのです。

integers は必ずしも配列である必要はありませんが、ここではInt(整数値)の配列ということにしておきます。

配列

Kotlinでは配列を使用するときに、Javaのように特別な記述はしません。integer の使用の宣言は次のように書けます。

  var integer : Array<Int>

Array という型に、型パラメータとして Int を指定しています。これは通常の文法であり、配列のためだけに用意された文法ではありません。

配列の要素にアクセスするためにはJavaと同様の記法で行います。つまり integers の先頭の要素を参照するには

  integers[0]

と書きます。実はこの記述、Kotlinではやはり配列用の文法ではありません。詳細は後述します。

クラス

KotlinはJavaと同じクラスベースオブジェクト指向言語なのでクラスを定義できます。ここでは配列のためのクラス Array のラッパークラスを作ります。 

class IntArrayWrapper(val length Int{
  private val array = Array<Int>(length) { 0 }
}

関数(あるいはメソッド*1)のようにクラス名の後に続けて引数が宣言されています。ここをプライマリ・コンストラクタ(基本コンストラクタ)と言い、ここに宣言されている変数はそのままそのクラスのプロパティとなります。この例では配列の長さをコンストラクタから受け取るようにしています。

private なプロパティとしてIntの配列を持っています(ここで作りたいのは配列のラッパですので)。

演算子オーバロード

先ほど作ったクラスに関数を追加して、外部へ操作を提供します。つまり関数を定義しましょう。

class IntArrayWrapper(val length : Int) {
  private val array = Array<Int>(length) { 0 }
  
  fun get(index : Int) = array[index]
  
  fun set(index : Int, value : Int) {
    array[index] = value
  }
}

get と set という名前の関数を定義しました。Kotlinの文法に従って記述していますが、なんとなく読めると思います。関数 get では引数に指定したインデックスで、ラップしている配列の要素にアクセスし返しているのがわかります。

 さて、これでIntArrayWrapper のインスタンスについて関数 get や set を呼び出せば配列の各要素にアクセスできるようになりました。

val array = IntArrayWrapper(4) // インスタンス生成
array.set(0, 12345)
println(array.get(0)) // 12345

しかし!なんと!これは次のように記述することもできます。

val array = IntArrayWrapper(4) // インスタンス生成
array[0] = 12345
println(array[0]) // 12345

Kotlinには演算子オーバロードという仕組みが備わっています*2。特定の演算子に対応するシグネチャで定義された関数を持つオブジェクトには、その演算子を用いて演算を行うことが可能です。例えば演算子 + に対応する関数名は plus といった具合です。ここでは get, set という関数が角括弧によるアクセスを可能にしています(これが演算子と言うかは微妙な感じですがw)。

ダックタイピング的イテレータとイテラブル

クラス IntArrayWrapper に要素をセットしたりゲットしたりする機能を追加しました。次にすることはIntArrayWrapper を冒頭で登場したfor文を使用して繰り返す機能の追加です。ご存知の通り、Javaではインタフェース Iterable を実装して Iterator の実装を返して実現しますね。Kotlinではその方法でも可能ですが、インタフェースを実装する必要はないのです。必要な関数さえ定義されていれば、それはもはやイテレータであると見なすのです。Java使いの中には聞き慣れない人もいらっしゃると思いますが、これをダックタイピングと言います。

次のコードが IntArrayWrapper 用のイテレータの定義です。 

class IntArrayWrapperIterator(val array : IntArrayWrapper) {
  
  private var current = 0
  
  fun next() = array[current++]
  
  fun hasNext() = current < array.length
}

拡張関数

イテレータの準備ができたので、IntArrayWrapper をイテラブルにするために関数 iterator を定義します。が、ちょっと待ってください。ちょっと変わった方法で定義します。クラス IntArrayWrapper の外側で定義したいと思います。次のコードはどのクラスにも属さない関数です。

fun IntArrayWrapper.iterator() = IntArrayWrapperIterator(this)

ここで定義した関数 iterator は、IntArrayWrapper のインスタンス関数であるかのように扱うことができます。このようにクラスの外部で定義でき、インスタンス関数のように扱える関数のことを拡張関数と言います。

拡張関数とダックタイピング的イテレータの組み合わせは非常に便利です。例えばKotlinの標準ライブラリではjava.lang.Stringに対してこの手法が使われています。Stringはfinal宣言されているためサブクラスを作れません。そこで拡張関数により関数 iterable が追加されています。そしてダックタイピングによりそのStringはIterableを実装されていると見なされるので、Kotlinではfor文でStringを扱えるのです(String#toCharArray()を使うよりも簡単でしょ?)。

気付いてた?

そろそろ名所見学ツアーはおしまいです。思ったよりも1つ1つの名所を説明しすぎたかな?代わりに回れる名所が少なくなってしまいましたが、Kotlinにはまだまだ魅力的な機能がたくさんあります!

今回登場したコード例で説明を省略したものがいくつかあります。例えばこんな感じ。

  • 文末のセミコロン不要
  • "counter : $i"のように文字列に式の評価値を埋め込めそう
  • 変数の型は変数名の後に(型推論に役立つ)
  • 変数宣言のキーワードが2つ(var と val)
  • 関数定義のキーワードはfun
  • 関数の本体ブロックの代わりに等号をはさんで式がある

気付いてましたか?明日以降はこのへんも紹介していきます!

今日作ったクラスや関数のすべてのコードを次に示します。使用例とエントリポイント(関数 main)も追記しておきますね。

fun main(args : Array<String>) {
  val array = IntArrayWrapper(3)
  array[0] = 5
  array[1] = 2
  
  for(i in array) {
    println(i)
  }
}
 
class IntArrayWrapper(val length : Int) {
  
  private val array = Array<Int>(length) { 0 }
  
  fun get(index : Int) = array[index]
  
  fun set(index : Int, value : Int) {
    array[index] = value
  }
}
 
fun IntArrayWrapper.iterator() = IntArrayWrapperIterator(this)
 
class IntArrayWrapperIterator(val array : IntArrayWrapper) {
  
  private var current = 0
  
  fun next() = array[current++]
  
  fun hasNext() = current < array.length
}

まとめと次回予告

今日はJavaにはないKotlinの特徴的な機能をざっと眺めました。重要な用語をおさらいすると、プライマリ・コンストラクタ、プロパティ、演算子オーバロード、拡張関数、あとおまけでダックタイピングが登場しました。明日はKotlinの開発環境についてお話しします。

日記

この記事書くだけでも3時間以上かかった...。結構ひとりアドベントカレンダーはキツイことがわかりました。6日までにAndroidの方のアドベントカレンダーの記事も書かなくては。

 

*1:このブログでは「関数」と呼ぶことにします。関数とメソッドの呼び分け方については正直コンテキストによって異なりますが、ここでは「関数」です。

*2:ScalaやGroovyにも演算子オーバロードはあります。しかし、Kotlinとの比較はしません。当アドベントカレンダーのサブタイトルの通り、Javaプログラマに対象者を絞っているからです。