Kotlin の String extension method の罠

Jun 10, 2020 03:04 · 680 words · 2 minute read

Kotlin の (やりすぎ説はあるが) 便利なところといえば、Collections や String の拡張関数が真っ先に浮かぶ筆者ですが、先日いつも通り便利に拡張関数を利用しながら Android 開発をしていたときに罠 (?) を踏み抜いたので記録に残しておこうかと思います。

String/CharSequence の拡張関数

結論から言うと、僕は StringCharSequence の拡張関数が自身を加工した結果として自身と同じ型の値を返す場合、その型は継承状態を保持していると思いこんでいましたが、そんなことはなかったという話です。

何を言っているのかというと、
例えば SpannedCharSequence を継承していて、 CharSequence.trimEnd()CharSequence を返しますが、その返却された値を必ずしも Spanned にキャストできるとは限らない、ということです。

“必ずしも” ということはできる場合とできない場合があるのですが、そのサンプルがこちらです。

// OK
SpannableStringBuilder("abc\n\n\n").trimEnd() as Spanned
SpannableStringBuilder("abc").trimEnd() as Spanned
// NG
SpannableStringBuilder("\n\n\n").trimEnd() as Spanned
SpannableStringBuilder("").trimEnd() as Spanned

なぜこのような違いが生まれるのかというと、trimEnd() の内部では 呼び出し元の文字列がすべて空白文字である 場合と 呼び出し元の文字列が空文字列である 場合には空文字の String インスタンスを新規生成して返却し、そうでない場合には元の文字列に対して subSequence() を呼び出した結果を返却するからです。

JetBrains の見解

YouTrack にてこの問題を報告してみた のですが、「それはそういう仕様だよ」と言われてしまいました。

当たり前といえば当たり前なのですが、先述の通りなまじうっかりキャストが成功するケースもあるので、もしも今までキャストができるものだと思いこんでいた僕のような方がいれば、それはたまたまであるということを留意いただければと思います。

おわりに

別に subSequence(0,0) で返せば良くない?

それでは!

tweet Share