Android Studio 3.0を使い、最近話題のプログラミング言語「Kotlin」の特徴を解説する連載。今回は、前回までで紹介し切れなかったKotlinの特徴的な機能を解説します。個人的なイチオシは「分解宣言と多重戻り値」です。
Android Studioを使い、Kotlin言語の特徴を解説する本連載「Android Studioで始めるKotlin入門」。連載最終回となる今回は、前回までで紹介し切れなかったKotlinの特徴的な機能を解説します。個人的なイチオシは「分解宣言と多重戻り値」です。Javaプログラマーにとってはちょっと驚く機能かもしれません。
Kotlinの拡張関数は、あるクラスを継承することなく、そのクラスにメソッドを追加する(ように見せる)機能です。例えばリスト1では、Catクラスにrunというメソッドを追加しています。
class Cat(val name: String){ private val age = 10 } //拡張関数。Catクラスの外にある関数で、Catクラスを拡張している fun Cat.run(){ //拡張関数の中では、thisを経由してCatクラスのプロパティを参照できる println("${this.name} is Running!") //エラー。privateプロパティは参照できない //println("${this.name}'s age is ${this.age}") } …… val cat = Cat("Tama") //拡張関数の呼び出し例。Catクラスにrunメソッドが存在するように書ける cat.run() //「Tama is Running!」
ここでは、Catクラスにrunメソッドを追加し、Catクラスのnameプロパティを参照しています。拡張関数で対象のオブジェクト(「レシーバーオブジェクト」と呼ばれる)を参照する場合はthisキーワードを使います。
リスト1でCatクラスにopenキーワードが付いていない、つまり継承不可であるにもかかわらず、runメソッドを追加できていることに驚かれるかもしれません。拡張関数は実際に対象のクラスにメソッドを追加しているわけではなく、外部に定義した関数を、対象となるクラスのメソッドのように見せ掛けているだけです。
その証拠に、Catクラスにprivateとして定義したageプロパティについては、拡張関数から参照することはできず、エラーとなります。拡張関数が本当にクラスのメソッドに追加されているわけではなく、あくまでも外部で定義した関数であることが分かるでしょう。
拡張関数の対象は自作のクラスだけに制限されておらず、Stringクラスのような標準ライブラリにも問題なく適用できます。例えばリスト2ではStringクラスにdumpというメソッドを追加しています。
//エラー。Stringクラスはfinalなので継承できない //class MyString: String(){} //拡張関数。Stringクラスにdumpというメソッドを追加する(ように見せる) fun String.dump(){ println("Length: ${this.length}, Content: $this") } …… //Stringクラスに拡張関数として追加したdumpメソッドを呼ぶ "Hello".dump() //「Length: 5, Content: Hello」
Stringクラス自体はfinalつまり継承不可能なクラスであるため、本来は直接継承してメソッドを追加できませんが、拡張関数を使うことで、自由にメソッドを拡張できます。このように拡張関数は、人が作ったライブラリについても、自分好みの関数を追加していける魅力的な機能です。Kotlin付属ライブラリでも多数の拡張関数が使われています。
とはいえ、あまり拡張関数を乱用すると他の人から読みづらいコードになる場合があるので、ご注意ください。
なお、C#にも「拡張メソッド」という同じような機能があります。ただしC#の場合はいったん何らかのクラスを入れ物として用意し、そのクラスの中にstaticメソッドとして拡張メソッドを定義する必要があります。クラス外に関数を定義できないC#としては仕方ないところですが、わざわざ入れ物の(それ自体を使うことはあまりない)クラスが必要なのが面倒なところです。
一方、Kotlinはもともとクラス外に関数を書けるため、拡張関数をより直感的に定義できます。
連載第2回で「for」を説明する際に触れましたが、Kotlinには「範囲」(Range)という概念があり、数値などの範囲を表現できます。幾つかの使い方を見てみましょう(リスト3)。
//1〜10の範囲 for(i in 1..10){ print(i) //「12345678910」 } //2〜10の範囲で2刻み for(i in 2..10 step 2){ print(i) //「246810」 } //9〜1の範囲(逆順)で2刻み for(i in 9 downTo 1 step 2){ print(i) //「97531」 } println() //1〜10の範囲。ただし終わりの値(10)は含まない for(i in 1 until 10){ print(i) //「123456789」 } println()
最初は基本パターンで、1〜10までを順に出力しています。「..」演算子を使って「1から10まで」を表す範囲を表現し、in演算子でそこから1つずつ値を取り出して処理しています。2つ目はstepを使って「2〜10の範囲。ただし2刻みで」という範囲を表現しています。3つ目は逆順を表すdownToを使い「9〜1の範囲(逆順)で2刻み」という範囲を表現しています。4つ目はuntilを使い「1〜10の範囲。ただし終わりの値(10)は含まない」という範囲を表現しています*1。
*1)なお、..やinは演算子ですが、step、downTo、untilなどは言語埋め込みのキーワードではなく拡張関数です。最後に触れる中置記法を使用しています。
「範囲」は整数値で使うことが多いですが、文字を表すCharクラスも(内部的に整数値であるため)対象とできます。リスト4は文字範囲を使ったサンプルです。
//a〜zの範囲 for(i in 'a'..'z'){ print(i) // 「abcdefghijklmnopqrstuvwxyz」 } //A〜zの範囲で2刻み for(i in 'A' until 'z' step 2){ print(i) // 「ACEGIKMOQSUWY[]_acegikmoqsuwy」文字コード的に大文字と小文字の間の文字も出力されている }
1つ目はaからzまで、2つ目はAからzまでで2刻みの範囲を表現しています。
「範囲」はforと組み合わせて繰り返しを表現する他に、条件判定にも使うことが多いでしょう。連載第2回で紹介した「when」のサンプルであるリスト9では、in演算子を使って、値が範囲内、範囲外にあるかどうかを判定していました。
こうした条件判定を範囲なしで書こうとすると、等号不等号とand、orなどの論理演算子を駆使する必要があるでしょう。等号不等号の組み合わせでバグを作り込むこともあるため、より直感的な書き方ができる「範囲」は便利ですね。
まとめると、範囲には以下の2つの用途があります。
このうち範囲チェックについては、整数値以外にもStringやDoubleなど、比較可能な任意のデータ型に適用可能です。しかし、forで使用するような反復処理については、Int、Long、Charクラスなどが返す整数型範囲(IntRange、LongRange、CharRangeというクラス)専用の機能になっていますのでご注意ください。
Copyright © ITmedia, Inc. All Rights Reserved.