KotlinでJavaみたいなメソッド参照をする
Javaでのメソッド参照、便利です。 例えばこんな感じのやつ。
// Java class Person { // 省略 String getName() { return name; } }
// Java
Optional<String> name = Optional.of(person).map(Person::getName);
すっごく便利ですよね!
これをKotlinでやろうとすると素直には行かない!
import java.util.Optional class Person(private val name: String) { // あえてプロパティではなくGetter fun getName(): String = name } fun main(args: Array<String>) { Optional.of(Person("Taro")).map(Person::getName) // コンパイルエラー }
上記の通り、map
の引数にPerson::getName
を渡そうとするとコンパイルエラーになってしまいます。
しかし、Kotlin的にはPerson::getName
のような書き方は存在するんです。が、Javaと意味は違ってきます。KotlinでPerson::getName
と書いたときの値の型はPerson.()->String
なのです!!
ことりん使いにはおなじみ( ? )ですが、これは「引数を取らずString
を返すようなPerson
のメソッド」を意味する型です。そうです、メソッドなのです!つまりレシーバが必要なのです!!シンプルな使用例はこんな感じ。
val taro = Person("Taro") val hoge: Person.()->String = Person::getName println(taro.hoge()) // => Taro
Person
型のtaro
がレシーバとなってPerson::getName
の別名hoge
メソッドを呼び出しています。
ここで最初の話題に戻って、map
の引数となるような関数、つまりここでは(Person)->String
型の値を得たいわけです。Person::getName
すなわちPerson.()->String
を(Person)->String
に変換できれば実現できますね。具体的にはこうです。
fun <A, B> toFunction(getter: A.()->B): (A)->B { return { (receiver: A) -> receiver.getter() } } fun main(args: Array<String>) { val name = Optional.of(Person("Taro")).map(toFunction(Person::getName)) println(name) // => Optional[Taro] }
toFunction
関数がPerson.()->String
を(Person)->String
に変換しています。ちょっと複雑なので、拡張メソッドにしてみましょう。
// メソッドの拡張メソッド fun <A, B> (A.() -> B).toFunction(): (A) -> B = { it.invoke() } fun main(args: Array<String>) { val name = Optional.of(Person("Taro")).map(Person::getName.toFunction()) println(name) // => Optional[Taro] }
おー!いいカンジですね!!
さて、Kotlinのクラスはプロパティを持てますが、話を簡単にするためにゲッターを自分で定義した例を示しました。プロパティを使うとPerson
クラスはこうなります。
class Person(val name: String)
このPerson
に対してはPerson::getName
が使えないので別の方法が必要です。
Person::name
と記述してプロパティを得ます。このような方法で得られたプロパティは次のように使います。
val taro = Person("Taro") val hoge = Person::name println(hoge.get(taro)) // => Taro
あとはメソッドのときと同様に変換を施せばいいわけです。
クラス名::プロパティ名
で得たプロパティの型はKMemberProperty<クラス, プロパティの型>
なので、次のような拡張メソッドを定義します。
// プロパティの拡張メソッド fun <A, B> KMemberProperty<A, B>.toFunction(): (A) -> B = { get(it) } fun main(args: Array<String>) { val name = Optional.of(Person("Taro")).map(Person::name.toFunction()) println(name) // => Optional[Taro] }
Kotlinでは関数リテラル(ラムダ式)には波括弧が必須なので、ちょっとした関数リテラルを書くにも{ }
があって邪魔な感じになってしまいます(個人の感想)。
今回紹介したような方法を使えば、関数リテラルを記述せずに済んでスッキリですね!
ちなみに今回やりたかったようなことを関数リテラルを用いて記述するとこうなります。
Optional.of(Person("Taro")).map { it.name }
好みの問題なのかな。 今回は以上です。
補足
@ngsw_taro 補足。メソッドの拡張メソッドtoFunctionとプロパティの拡張メソッドtoFunctionを、それぞれ「_」という名前の拡張プロパティとして定義して読みやすくするという工夫もあるかも
— たろう (@ngsw_taro) 2015, 2月 24
つまりこういうことです。
val <A, B> KMemberProperty<A, B>._: (A) -> B get() = { get(it) } fun main(args: Array<String>) { val name = Optional.of(Person("Taro")).map(Person::name._) println(name) // => Optional[Taro] }