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による型チェック- そのブロック内では
objがStringであることが保証される - その結果、
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についてでした。
最後までお読みいただき、ありがとうございました。










