算譜王におれはなる!!!!

偏りはあると思うけど情報技術全般についてマイペースに書くよ。

#Kotlin の拡張関数の優先度についてメモ

Kotlinの拡張関数は、現時点ではレシーバとなるオブジェクトを第1引数に取るstaticメソッドとしてバイトコードにコンパイルされる。ということは、既存のメンバ関数と同一のシグネチャをもった拡張関数を定義することが可能*1。ここで疑問。同一シグネチャのメンバ関数と拡張関数が用意されているとき、オブジェクトに対してそのシグネチャの関数を呼び出すとどちらの処理が実行されるのか。実験してみた!

環境はKotlin Web Demo(バージョンは0.1.3065)。
次のコードを書いて、コンパイル・実行してみた。

fun main(args : Array<String>) {
    val greeterImpl : GreeterImpl = GreeterImpl()
    greeterImpl.greet()
    greeterImpl.greet("world")

    val greeter : Greeter = greeterImpl
    greeter.greet()
    greeter.greet("world")
}

trait Greeter {
    fun greet()
}

class GreeterImpl() : Greeter {
    override fun greet() {
        println("Hello")
    }
}

fun Greeter.greet() {
    println("Good morning")
}

fun GreeterImpl.greet() {
    println("Good afternoon")
}

fun Greeter.greet(target : String) {
    println("Hello, $target!")
}

fun GreeterImpl.greet(target : String) {
    println("Hi, $target!")
}

出力結果は

Hello
Hi, world!
Hello
Hello, world!


出力結果からわかることは、どうやら拡張関数よりもメンバ関数を優先して呼び出すみたい。つまり、拡張関数を既存のメンバ関数のオーバライドとして使用することはできないってこと(overrideアノテーションを付けてもダメ。というか付けられない)。
また、GreeterとGreeterImplそれぞれの拡張関数として定義されている関数greet(String)があるけど、これの呼び出しルールは少し面白い!greet(String)の呼び出しをGreeterImpl型経由で行うかGreeter型経由で行うかで結果が異なる。呼び出しの記述がメンバ関数の場合と同じなので、不思議に思えるけど、拡張関数の正体は冒頭で述べたとおりなので、当然と言えば当然の結果。

*1:hoge(f : Fuga)というメンバ関数に対して、hoge(f : Fuga)という拡張関数を定義できるということ 。