Kotlinの型チェックは、安全性・可読性・保守性を高めるための中核機能です。
Javaと比べても非常に洗練されており、「コンパイル時にできる限りバグを潰す」という思想が強く反映されています。
ここでは 基礎 → 実践 → 応用 → よくある落とし穴 の流れで、Kotlinの型チェックを体系的に解説します。
Kotlinにおける型チェックの全体像
Kotlinの型チェックは、主に次の5つの要素で構成されています。
- 静的型付け(Static Typing)
- 型推論(Type Inference)
- null安全(Null Safety)
- スマートキャスト(Smart Cast)
- 型チェック・キャスト演算子(is / as)
これらが相互に連携することで、「記述量は少ないが、実行時エラーは起きにくい」というKotlinらしい設計が実現されています。
静的型付け:コンパイル時に型の整合性を検証
Kotlinは静的型付け言語です。
変数の型はコンパイル時に決定され、型の不整合は実行前に検出されます。
val age: Int = 20
age = "20" // ❌ コンパイルエラー
特徴
- 実行前に型エラーを検出できる
- Javaのような広範な暗黙の型変換は行われない
- 型安全性が非常に高い
val num: Int = 10
val result = num + 1.5 // ❌ Int と Double は直接演算できない
この場合は、明示的な型変換が必要です。
val result = num.toDouble() + 1.5
型推論:省略できても型は厳密
Kotlinでは多くの場合、型を明示的に書かなくてもコンパイラが自動的に推論します。
val name = "Kotlin"
val count = 10
型推論のメリット
- 冗長な型指定を減らせる
- コードが読みやすくなる
- 型安全性は維持される
複数の型が混在する場合
val list = listOf(1, 2.0)
この場合、Kotlinは要素の共通スーパータイプを計算し、List<Number> と推論します。
数値を自動的に Double に変換するような暗黙変換は行われません。
必要に応じて、次のように明示することも可能です。
val list: List<Number> = listOf(1, 2.0)
null安全と型チェック(Kotlin最大の特徴)
Kotlinでは、nullを許容する型と許容しない型が明確に区別されています。
var name: String = "Alice"
name = null // ❌ コンパイルエラー
Nullable型
var name: String? = "Alice"
name = null // OK
Nullable型は、そのままではメンバにアクセスできません。
val length = name.length // ❌ エラー
安全な呼び出し
val length = name?.length
Elvis演算子
val length = name?.length ?: 0
is演算子による型チェック
Kotlinでは instanceof の代わりに is 演算子を使います。
if (obj is String) {
println(obj.length)
}
is は型チェックを行い、その結果をもとに コンパイラが安全と判断できる場合、後続の処理でスマートキャストが働きます。
スマートキャスト(自動型変換)
スマートキャストは、Kotlinの型チェックを強力にしている機能の一つです。
fun printLength(obj: Any) {
if (obj is String) {
println(obj.length) // Stringとして扱える
}
}
スマートキャストが有効になる条件
- 型チェック後に、その変数の型が変わらないとコンパイラが証明できる
- ローカル変数である
- 再代入や外部からの変更が行われない
注意点
var だから必ずスマートキャスト不可、というわけではありません。
ただし、プロパティや再代入の可能性がある変数では、安全性が保証できずスマートキャストが無効になるケースが多くなります。
as演算子による明示的キャスト
通常のキャスト
val str = obj as String
型が一致しない場合、ClassCastException が発生します。
安全キャスト(as?)
val str = obj as? String
キャストに失敗すると null が返り、例外は発生しません。
val length = (obj as? String)?.length ?: 0
when式と型チェック
when 式は型チェックとの相性が非常に良く、可読性の高い分岐処理が書けます。
fun handle(obj: Any) {
when (obj) {
is String -> println("String: ${obj.length}")
is Int -> println("Int: $obj")
else -> println("Unknown")
}
}
- 条件分岐が整理される
- 各分岐でスマートキャストが効く
- if-else の連鎖を避けられる
ジェネリクスと型チェック
型消去の影響
JVMの制約により、ジェネリクスの型引数は実行時に消去されます。
if (list is List<String>) { } // ❌ 実行時には判定できない
回避方法(reified)
inline fun <reified T> isType(obj: Any) = obj is T
reified を使うことで、型情報を実行時にも扱えるようになります。
Kotlinの型チェックでよくある落とし穴
Anyは「何でもできる型」ではない
val obj: Any = "Hello"
obj.length // ❌
Any で使えるのは Any 自身に定義されたメンバのみです。
!! は最後の手段
val length = name!!.length
これはnull安全を破壊し、実行時クラッシュの原因になります。
可能な限り ?. や ?: を使うべきです。
Javaとの相互運用(プラットフォーム型)
Java由来の型は nullability が不明なため、IDE上で プラットフォーム型(例:String!)として表示されます。
これはKotlinのソースコードに書ける型ではなく、「nullかもしれないし、そうでないかもしれない」型を示す概念です。
まとめ
Kotlinの型チェックは、
- コンパイル時安全性
- 明確なnull管理
- スマートキャストによる簡潔な記述
- 読みやすい型分岐
を同時に実現する、非常に完成度の高い仕組みです。
この理解を土台に、sealed class・分散型設計・共変/反変へ進むことで、Kotlinをより安全かつ表現力豊かに使いこなせるようになります。
以上、Kotlinの型チェックについてでした。
最後までお読みいただき、ありがとうございました。










