スマートキャストは、Kotlinコンパイラが特定の型またはnullのチェックの後に、型を自動的に「ダウンキャスト」するメカニズムです。これにより、明示的な変換なしに、キャストされた型のプロパティやメソッドを使用できます。
fun demo(x: Any) { if (x is String) { println(x.length) // xは自動的にStringにキャストされる } }
機能するもの:
is / !is演算子(ifやwhen内で)if (x != null))when式機能しないもの:
val str: String? = getString() if (str != null) println(str.length) // スマートキャスト
val something: Any get() = fetch() if (something is String) { println(something.length) // エラー: スマートキャストが不可能 }
メソッド内でのクラスのopenまたはvarプロパティに対してスマートキャストを期待できますか?
いいえ!スマートキャストはローカル変数とfinalクラスのvalプロパティにのみ機能します。オープン(open)やvarプロパティに対しては、コンパイラは他のスレッドやサブクラスによって値が変更される可能性があるため確信が持てません。これらには手動キャストまたはローカル変数が必要です。
open class Base { open var maybeString: Any? = "abc" fun check() { if (maybeString is String) { // println(maybeString.length) // エラー: スマートキャストが不可能 val asString = maybeString as String println(asString.length) // 明示的キャスト } } }
物語
あるプロジェクトでは、画面のロジックにopen varプロパティのデータモデルを使用していました。プログラマーはif (model is SomeType)のチェックの後にスマートキャストを信頼していましたが、コンパイル時にすべて手動で型をキャストしなければならなかったため、可読性が低下し、コードの重複が生じました。スマートキャストに対するvar/openの制限を知らなかったためです。
物語
ゲッターを介してデータを取得する際に(例えば、デリゲートやバリデーションを通じて)、値に対するスマートキャストが機能しませんでした。開発者はその理由を理解できず、デバッグに数時間を費やしましたが、コンパイラがそのようなゲッターにスマートキャストを適用しない理由を理解するまで時間がかかりました。呼び出し間で異なる値を返す可能性があるためです。
物語
プロジェクトでnullable値をif (obj != null)で処理する際、スマートキャストはブランチ内で機能しましたが、コードを並列化した際には保証されない範囲でのアクセスからNullPointerExceptionが発生しました。これは、スマートキャストのローカル動作とnullable変数に対するマルチスレッドシナリオの特性についての理解が不足していることを示しました。