この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
Kotlinいいですよね。
少し前の- 第6回Kotlin勉強会 @ Sansan - connpass で拡張関数のガイドラインの発表がありました。とても良い資料だと思います。最近だんだんKotlin事例が多くなってきたなので、再掲してもっと広まってほしいです。
ちょっとわかりにくい所を少し補足的に付け足したいと思います。
interfaceに拡張関数に書く
スライドの「interface内で拡張関数定義」の部分の補足を足したい思います。
逆にわかりにくくなっているかも?
interfaceを実装しクラス内で使えるようにする
拡張関数は好きなところにかけます。静的に解決されるので注意が必要です。 オレオレ拡張関数の無法地帯だと、思わぬ罠にはまります。 それは、スライドで言われている通りです。
JavaでUtilクラスレベルのものは、グローバルなところに書いてOKだと思いますが、コンテキストが必要な場合は、他の人が使用すとき混乱してしまうかもしれません。
たとえば、Intに時間に関する拡張関数を書いたとします。
val Int.seconds get() = this * 1000
val Int.minutes get() = this.seconds * 60
1.minutes // => 60000
一見良さそうに見えますが、暗黙の前提としてms(ミリ秒)である必要があります。
要件によって、人によっては、s(秒)が基準で6.minutesの期待値が60かもしれません。しかし、すでに存在するのでどちらかを諦めるか、別の名前にするかです。
前提が必要な場合は、interfaceに閉じ込める方法がベターです。 英語力のなさが露呈しますが、以下のようなinterfaceをつくります。
// msであることを前提とする、拡張関数をまとめる
interface TimeMsInterface {
val Int.seconds get() = this * 1000
val Int.minutes get() = this.seconds * 60
}
使用する場合は、interfaceを実装する。そのclass内で使えるようになります。
class Hoge : TimeMsInterface {
val foo = 1.minutes
fun hiyo() = 1.seconds
}
また、拡張関数をつかえるスコープを限定するとIDEにムダに保管されることもありません。
一時的に拡張関数をつかえるようにする
クラスで実装するまででなく、一時的に使い場合があります。
スライドで乗っている方法だと
interface TimeMsInterface {
val Int.seconds get() = this * 1000
val Int.minutes get() = this.seconds * 60
}
fun calcTime(f: TimeMsInterface.() -> Int) = object : TimeMsInterface {}.f()
calcTime { 1.minutes } // => 60000
???っとなると思いますので、補足をします。
//object式で無名クラスのオブジェクトを作成をします
val timeMsInterface = object : TimeMsInterface {}
//runをつかってレシーバー付ラムダなのでthisがTimeMsInterfaceになることで、TimeMsInterfaceの内部を使用することが出来る
timeMsInterface.run { 1.minutes } // => 60000
このまま関数化する
fun calcTime(f: TimeMsInterface.() -> Int): Int {
val timeMsInterface = object : TimeMsInterface {}
return timeMsInterface.run(f)
}
f: TimeMsInterface.() -> Int は、レシーバーがTimeMsInterfaceになるということだけど、TimeMsInterfaceに一時的な拡張関数f()を作ると考えても良いので。
fun calcTime(f: TimeMsInterface.() -> Int): Int {
val timeMsInterface = object : TimeMsInterface {}
return timeMsInterface.f() //TimeMsInterfaceに一時的なf()メソッドを使用
}
わざわざ2行にする必要もないので
fun calcTime(f: TimeMsInterface.() -> Int) = object : TimeMsInterface
{}.f()
返り値がInt限定にしてるので、Intを返す拡張関数しか使えません。少し発展させてどの拡張関数でも問題ないようにします。
fun<T> calcTime(f: TimeMsInterface.() -> T) = object : TimeMsInterface {}.f()
まとめ
拡張関数はとっても便利だけど、オレオレ度控えめで便利に使っていきましょう。