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]
}