Kotlinのスマートキャストについて

採用はこちら

Kotlinのスマートキャスト(Smart Cast)は、単なる糖衣構文ではなく、型安全・null安全・可読性を同時に成立させるための中核機能です。

一方で、

  • 「なぜこれはスマートキャストされないのか?」
  • var だとダメなのか?」
  • 「プロパティだと通らない理由は?」

といった疑問でつまずく人が非常に多いのも事実です。

本稿では基礎 → 成立条件 → 成立しない理由 → 実務での回避設計という流れで、正確かつ誤解のない形で解説します。

目次

スマートキャストとは何か

スマートキャストとは、

コンパイラが「この地点では型が確定している」と静的に証明できる場合、
明示的なキャストを書かなくても、その型として扱えるようにする仕組み

です。

Javaとの比較

if (obj instanceof String) {
    String s = (String) obj;
    System.out.println(s.length());
}

Kotlin(スマートキャスト)

if (obj is String) {
    println(obj.length)
}

ポイントは以下の2点です。

  • 実行時の魔法ではない
  • コンパイル時に100%安全だと判断できる場合のみ適用される

スマートキャストが成立する基本パターン

is / !is による型チェック

fun printLength(x: Any) {
    if (x is String) {
        println(x.length) // x は String にスマートキャスト
    }
}

否定条件でも成立します。

if (x !is String) return
println(x.length)

when 式での型分岐

fun handle(x: Any) {
    when (x) {
        is String -> println(x.length)
        is Int -> println(x + 1)
        else -> println("Unknown")
    }
}

各ブランチ内では x の型が確定します。

null安全とスマートキャスト

Kotlinでは nullチェックも型チェックの一種として扱われます。

fun printLength(s: String?) {
    if (s != null) {
        println(s.length) // s は String にスマートキャスト
    }
}
  • String? → nullable
  • String → non-null

という型システムがあるため、null チェック後は 非null型として安全に扱えるようになります。

スマートキャストの本質条件:「安定性(stability)」

ここが最重要ポイントです。

Kotlinは次の問いに YES と証明できる場合のみ スマートキャストを行います。

「この式の値は、チェック後に変わらないと静的に保証できるか?」

この「変わらない」という性質を 安定(stable) と呼びます。

var とスマートキャスト(正確な理解)

よくある誤解
var だからスマートキャストできない」

これは 不正確 です。

正しい理解

  • ローカル変数の var
    チェック後に値が変更されないとコンパイラが証明できれば スマートキャストされる
  • 変更される可能性があると判断された場合 は不可

成立する例(ローカル var

fun f() {
    var x: Any = "hello"
    if (x is String) {
        println(x.length) // 成立するケースが多い
    }
}

成立しない例(変更され得る)

var x: Any = "hello"
if (x is String) {
    x = 123
    println(x.length) // 不可
}

ラムダにキャプチャされる場合

var x: Any = "hello"
val change = { x = 123 }

if (x is String) {
    change()
    println(x.length) // 不可(他経路で変わる可能性)
}

プロパティでスマートキャストされにくい理由

プロパティは、ローカル変数よりも 不安定になりやすい ため、スマートキャストされないケースが増えます。

代表的に「不安定」と判断される条件

  • var プロパティ(外部から変更され得る)
  • カスタム getter(呼ぶたび結果が変わる可能性)
  • open プロパティ(サブクラスでオーバーライドされ得る)
open class Sample {
    open val data: Any = "hello"
}

fun test(s: Sample) {
    if (s.data is String) {
        println(s.data.length) // スマートキャスト不可になりやすい
    }
}

※ 「public だからダメ」なのではありません。
同じ値である保証がコンパイラにできないのが本質です。

確実にスマートキャストを成立させる設計パターン

一時変数に受ける(最も堅牢)

val d = s.data
if (d is String) {
    println(d.length)
}

安全キャスト as? を使う

val s = obj as? String
if (s != null) {
    println(s.length)
}

コレクションとスマートキャスト

val list: List<Any> = listOf("a", 1, "b")

val strings = list.filterIsInstance<String>()
strings.forEach {
    println(it.length) // it は String
}

filterIsInstance<T>() は、スマートキャストを前提とした型安全APIです。

コントラクト(Contracts)によるスマートキャスト補助

Kotlin標準ライブラリの一部関数は、「この関数を通過した後の状態」 をコンパイラに伝えています。

fun printLength(s: String?) {
    requireNotNull(s)
    println(s.length)
}

requireNotNull は「ここ以降、s は null ではない」という情報を提供します。

スマートキャストの思想

  • スマートキャストは コンパイル時のみ
  • 「人間が見て明らか」でも
    コンパイラが証明できなければ適用されない
  • その厳しさが Kotlinの型安全を支えている

まとめ

  • スマートキャストの鍵は 安定性(stability)
  • val + ローカル変数が最も成功率が高い
  • var でも「変更されない」と証明できればOK
  • プロパティは不安定になりやすいので一時変数に逃がす
  • 「なぜ失敗するか」を理解すると設計が洗練される

以上、Kotlinのスマートキャストについてでした。

最後までお読みいただき、ありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次