はじめに
Kotlin のジェネリクスは Java と同様に「型消去方式」 を採用しています。
つまり、実行時にはジェネリクスの型情報が存在しません。
(コンパイル時だけ使われる仕組みです。)
1. 型消去とは何か?
定義:
型消去(Type Erasure) とは、
コンパイル後にジェネリクスの型パラメータ情報が削除され、
実行時には「生の型(raw type)」だけが残る仕組み。
つまり:
List<String> → List
List<Int> → List
実行時にはどちらも 同じ型 (List) になります。
2. なぜ型が消えるのか?
Kotlin は JVM 上で動くため、
Java と同じ「型消去」方式 を採用しています。
Java が型消去を採用した理由:
- 後方互換性(Generics 導入前の Java バイトコードと共存するため)
- 実行時のオーバーヘッドを避けるため
したがって、JVM 上ではコンパイル時の型安全性のみ保証され、
実行時には型情報が削除されるという仕組みになっています。
3. 実際の例
例1:リストの型判定ができない
fun <T> check(list: List<T>) {
if (list is List<String>) { // ❌ エラー!
println("This is List<String>")
}
}
エラーメッセージ:
Cannot check for instance of erased type 'List<String>'
理由:
実行時には List<T> も List<String> も単なる List になるからです。
例2:実行時には型が同じになる
val list1 = listOf("a", "b")
val list2 = listOf(1, 2)
println(list1::class == list2::class) // true
実行時にどちらも java.util.ArrayList 型。
つまり List<String> と List<Int> の区別は完全に失われます。
4. 型消去が起こるとどうなる?
| 状況 | 結果 | 理由 |
|---|---|---|
is List<String> |
❌ 不可 | 実行時には List しか残らない |
when (list) で型分岐 |
❌ 不可 | 同上 |
T::class を使う |
❌ 不可 | 型パラメータ T は消える |
value!!::class を使う |
✅ 可 | 実際のオブジェクトの型は保持されている |
5. 実際に消える部分
fun <T> printList(list: List<T>) {
println(list.javaClass.name)
}
出力:
java.util.Arrays$ArrayList
T の情報 (String, Int など) は完全に消え、
List の実装クラスだけが残ります。
6. 対処法:reified 型パラメータを使う
Kotlin では inline 関数+reified を使うことで、
実行時に型情報を保持 できます。
例:reified による型チェック
inline fun <reified T> checkType(value: Any) {
if (value is T) {
println("✅ value is ${T::class.simpleName}")
} else {
println("❌ value is not ${T::class.simpleName}")
}
}
checkType<String>("Hello") // ✅ value is String
checkType<Int>("Hello") // ❌ value is not Int
reified(具象化)によって、
コンパイル時に型情報がインライン展開され、
T::class や is T が使えるようになります。
例:reified を使った JSON パース
inline fun <reified T> parseJson(json: String): T {
val type = object : TypeToken<T>() {}.type
return Gson().fromJson(json, type)
}
ランタイムでも T の型がわかるので、
List<User> などの複雑な型にも対応可能です。
7. Kotlin での型消去の整理表
| 項目 | 型情報保持 | 備考 |
|---|---|---|
| コンパイル時 | 型安全にチェックされる |
List<String> は List<Int> に代入できない |
| 実行時 (JVM) | 型情報は消える |
List<String> と List<Int> は同一扱い |
| reified 使用時 | 型情報を埋め込める |
inline 関数でのみ可能 |
8. 型消去の利点と欠点
| 観点 | 利点 | 欠点 |
|---|---|---|
| パフォーマンス | バイトコードが軽量、後方互換性維持 | - |
| 型安全性 | コンパイル時に検証可能 | 実行時に型を判定できない |
| 柔軟性 | 既存 Java ライブラリとの互換性 | 型推論が限定される |
まとめ
- Kotlin(JVM)は ジェネリクス型消去方式 を採用
- 実行時には型パラメータが削除され、
List<String>もList<Int>も同一型扱い - 型チェック(
is List<String>)は不可能 - 実行時にも型を使いたいときは
inline fun <reified T>を使う