KotlinのSmartCastについて

採用はこちら

Kotlinの Smart Cast(スマートキャスト) は、単なる「便利機能」ではありません。

これは Kotlinコンパイラが行う制御フロー解析(control flow analysis) に基づいた、非常に厳密な型安全機構です。

「なぜキャストを書かなくていいのか」「なぜある場所ではSmart Castされるのに、別の場所ではされないのか」

これらを正しく理解することで、Kotlinコードの可読性・安全性・設計レベルは一段階上がります。

目次

Smart Castとは何か

Smart Castとは、コンパイラが「この時点では型が確定している」と論理的に証明できた場合に、自動的にその型として扱ってくれる仕組みです。

基本例

val obj: Any = "Hello"

if (obj is String) {
    println(obj.length) // 明示的キャスト不要
}
  • is String による型チェック
  • そのブロック内では objString であることが保証される
  • その結果、String のメンバに直接アクセスできる

これは 実行時の暗黙キャスト ではなく、コンパイル時に安全性が証明されたコードだけを許可する仕組みです。

Smart Castが成立する基本条件

Smart Castが成立するかどうかは、次の1点に集約されます。

「その参照が、この時点以降も同じ値・同じ型であると、コンパイラが証明できるか」

この条件を満たすときのみ、Smart Castは有効になります。

Smart Castが有効になる代表的な構文

if / else による型チェック

if (value is Int) {
    println(value + 1)
}

when 式(型分岐との相性が非常に良い)

when (value) {
    is String -> println(value.length)
    is Int -> println(value + 1)
    else -> println("Unknown")
}

when は Kotlinらしい設計と非常に相性が良く、Smart Castの恩恵を最大限に受けられます。

nullチェック(Nullable型のSmart Cast)

val text: String? = "Hello"

if (text != null) {
    println(text.length) // String として扱われる
}
  • String?String
  • nullチェックもSmart Castの一種

論理演算子(短絡評価)

if (obj is String && obj.length > 3) {
    println(obj.uppercase())
}

Kotlinは短絡評価を理解しているため、&& の右辺でもSmart Castが成立します。

Smart Castが成立しないケース(ここが最重要)

Smart Castが効かない理由は、ほぼすべて次に集約されます。

「その参照が途中で変わらないと、コンパイラが保証できない」

以下は代表的なケースです。

var で、値が変更され得る場合

var x: Any = "Hello"

if (x is String) {
    x = 123
    println(x.length) // コンパイルエラー
}
  • var は再代入可能
  • 型の一貫性を保証できないためSmart Cast不可

※重要なのは「var だから」ではなく「値が変わり得る参照であること」です。

カスタムgetterを持つプロパティ

val value: Any
    get() = "Hello"

if (value is String) {
    value.length // Smart Castされない
}
  • getterが呼ばれるたびに別の値を返す可能性
  • 同じ値であると証明できない

open / override 可能なプロパティ

open class A {
    open val x: Any = "Hello"
}

fun test(a: A) {
    if (a.x is String) {
        a.x.length // Smart Cast不可
    }
}
  • サブクラスで挙動が変わる可能性
  • Kotlinは保守的に判断する

委譲プロパティ(by lazy など)

val x: Any by lazy { "Hello" }

if (x is String) {
    x.length // Smart Castされない
}
  • 委譲先の実装に依存
  • 値の安定性を保証できない

ラムダにキャプチャされた変数

var x: Any = "Hello"
val f = { println(x) }

if (x is String) {
    x.length // Smart Castされないことがある
}
  • クロージャが変数を保持
  • 別経路で変更される可能性が否定できない

Smart Castを成立させる実践的テクニック

可能な限り val を使う

val x: Any = "Hello"
if (x is String) {
    println(x.length)
}

Smart Castを最大限活かすための最重要原則です。

ローカル変数に退避する

val tmp = obj
if (tmp is String) {
    println(tmp.length)
}
  • プロパティでは効かない場合の定番回避策
  • when (val v = ...) も非常に有効

明示的キャスト(最終手段)

val s = obj as String

安全キャスト

val s = obj as? String
println(s?.length)

Smart CastとKotlin Contracts

通常、関数をまたぐとSmart Cast情報は失われます。

fun isString(v: Any?): Boolean = v is String

if (isString(obj)) {
    obj.length // Smart Castされない
}

Contractsを使うと型情報を伝えられる

@OptIn(ExperimentalContracts::class)
fun isString(v: Any?): Boolean {
    contract {
        returns(true) implies (v is String)
    }
    return v is String
}
if (isString(obj)) {
    obj.length // Smart Cast成立
}

注意

  • Contractsは現在も Experimental
  • 利用には opt-in が必要
  • APIや挙動は将来変更される可能性あり

Smart Castがもたらす実務上の価値

  • キャストコードが不要 → 可読性向上
  • 実行時キャストエラーを防止
  • null安全と型安全を同時に実現
  • when + sealed class と組み合わせると強力

まとめ

  • Smart Castは 制御フロー解析に基づくコンパイル時型推論
  • 成立条件は「値が変わらないと証明できるか」
  • val / if / when / nullチェックが基本
  • プロパティ・getter・委譲・キャプチャには注意
  • Contractsで拡張可能(ただしExperimental)

以上、KotlinのSmartCastについてでした。

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

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