Kotlin

[Kotlin] 拡張関数をサブクラスから参照する

概要

親クラスの拡張関数を子クラスから super.function() の形式で呼ぶことは文法上できない。
子クラスの同名関数で親クラスの拡張関数が隠蔽される場合は、若干ひねった呼び方をする必要がありそう。

サンプルコード

関数参照またはキャスト

open class Parent
fun Parent.f() = "parent"

class Child0: Parent() {
    fun test() {
        // 通常は子クラスからも this 経由で Parent.f() にアクセスできる
        println(f())                  // => "parent"
        println(this.f())             // => "parent"
    }
}

class Child: Parent() {
    // 同名の関数で隠蔽
    fun f() = "child"
    fun test() {
        // 子クラス側の定義が優先される
        println(f())                  // => "child"
        println(this.f())             // => "child"

        // super 経由の参照はコンパイルエラーになる
        // error: 'super' is not an expression, it can not be used as a receiver for extension functions
        println(super.f())

        // 関数参照を使えば呼べる
        println((Parent::f)(this))    // => "parent"

        // 親クラスにキャストしてもよい
        println((this as Parent).f()) // => "parent"
    }
}

Child.f() を拡張関数として定義した場合も、その定義が Child クラスから参照できる状態であれば同様の挙動となる。
なお、逆に Child クラスが拡張関数 Child.f() を参照できない構成では、ChildChild.f() の定義を同時にインポートしたとしても、そもそも Child クラス内の this.f()Parent.f() を参照したままになる。

outer.kt
package outer

open class Parent
fun Parent.f() = "parent"

class Child: Parent() {
    fun test() {
        println(this.f())
    }
}
Main.kt
package inner
import outer.*

// オーバーライドめいたことをする
fun Child.f() = "child"
fun main(args: Array<String>) {
    Child().test() // => "parent"
}

これは、this.f() が静的に Parent.f() として解決されることによる。

インポート文で別名を付ける

パッケージ構成によっては import ~ as で Parent.f() に別名を付けて対処することもできる。

Parent.kt
package parent

open class Parent
fun Parent.f() = "parent"
Child.kt
package child

import parent.Parent
import parent.f as parentf

class Child: Parent() {
    fun f() = "child"
    fun test() {
        println(parentf()) // => "parent"
    }
}

ただし、Parent.f()Child.f() が同一パッケージにあり Child.f() が拡張関数であるような場合は、Child.f() に対しても別名が適用されてしまうので、おそらくこの方法では対処できない。
(トップレベルでなくクラス内に拡張関数が定義されていれば対処可能な場合もあるかも、未確認)

隠蔽される関数を呼ぶだけの関数を定義する

もっと原始的な方法として、Parent に別名の拡張関数を定義しなおす方法もある。

class Child: Parent() {
    // この文脈での this は Parent のインスタンスを指す
    // 紛らわしければトップレベルで定義してもよい
    private fun Parent.parentf() = f()
    fun f() = "child"
    fun test() {
        println(parentf()) // => "parent"
    }
}

Parent.f() を何度も呼び出すような場合は、普通にこうするのが最善かもしれない。

注意点

これらの対処はすべて拡張関数が静的に解釈されることに依拠しているので、Parent.f() が拡張関数でない場合は (Parent::f)(this)(this as Parent).f()"child" を返すことに注意。
拡張関数の場合は (Parent::f)(this)、そうでない場合は通常通り super.f() というように書き分ける必要がある。

言語仕様

なぜ super で参照できないようになっているのかはよく分からないが、仕様である。

Kotlin Language Specification - 16.17. super Access

Super access cannot be used to access an extension.

まあそういうものだと思って受け入れるしかなさそう。