拡張 - Kotlin Programming Language
リファレンスにある拡張関数について、ハマったことがあったので、調べてみました。
追加したNull許容レシーバーの処理が実行されない
例えば以下のソースコードがあったとします。
class C {
fun foo() { println("member") }
}
// 今回実行したいNull許容レシーバー
fun C?.toSring(): String { return "extension" }
fun main() {
var c: C? = null
println(c.toString()) // extensionではなくnullが出力されてしまう
}
Null許容型と非null許容型は明確に区別される
リファレンスによると、
拡張は、null許容なレシーバの型で定義できることに注意してください。このような拡張は、その値がnullの場合でも、オブジェクト変数で呼び出すことができ、かつその本体内で this == null をチェックすることができます。
だそうです。例えば以下のコードのように、
class C {
fun foo() { println("member") }
}
// 今回実行したいNull許容レシーバー
fun C?.toSring(): String { return "extension" }
// CクラスにあるメソッドをNull許容レシーバーとして追加
fun C?.foo() { println("aaaaaa") }
fun main() {
var c: C? = null
println(c.toString()) // extensionではなくnullが出力されてしまう
c.foo() // aaaaaa
var c2 = C()
c2.foo() // member
}
cはnullなので、Null許容レシーバーとして追加した方が呼ばれ、c2はメンバメソッドが呼ばれます。
この理論でいけば、toString()なんかも同様の結果になるはずですが、実際はそうはなりませんでした。
toString()はAnyクラスに存在する関数だから?
Kotlinにおいて、全てのクラスはAnyクラスの子クラスであります。
Any - Kotlin Programming Language
Anyクラスは、toString()や、hashCode()などのメンバメソッドを持っています。
また、拡張関数の特徴として、
もし、あるクラスがメンバ関数を持つうえ、さらに、同じレシーバ型、同じ名前を有し、与えられた引数を受容可能な拡張関数が宣言されると、 常にメンバが優先されます。
があります。
そのため、拡張関数toString()を追加したとしても、すでにAnyクラスに定義済みのtoString()が呼ばれてしまっているため、反映されないのではと考えます。
拡張関数の説明に関しては、以下のスライドがわかりやすいと感じました。
みんな大好き拡張関数 #kotlin_sansan - Speaker Deck