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?→ nullableString→ 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のスマートキャストについてでした。
最後までお読みいただき、ありがとうございました。









