はじめに
Kotlin のジェネリクスは JVM の制約により 実行時に型が消える(型消去) 方式ですが、
inline と reified を組み合わせることで 実行時にも型を扱う ことができます。
1. 背景:型消去の問題
通常のジェネリック関数では、実行時に型情報は消えてしまいます。
fun <T> checkType(value: Any) {
if (value is T) { // ❌ コンパイルエラー
println("Yes, it's T")
}
}
⚠️ コンパイルエラー:
Cannot check for instance of erased type 'T (of fun <T> checkType)'.
これは 型パラメータ T が実行時に存在しない(消去される) ためです。
これを解決するのが reified 型パラメータです。
2. reified 型パラメータとは?
reified(リファイド)は「具象化された」という意味。
つまり、「実行時にも型情報を保持する」ことができます。
基本構文
inline fun <reified T> checkType(value: Any) {
if (value is T) { // ✅ 使える!
println("This is ${T::class.simpleName}")
} else {
println("Not ${T::class.simpleName}")
}
}
checkType<String>("Hello") // ✅ This is String
checkType<Int>("Hello") // ❌ Not Int
通常は不可能な is T や T::class が、reified によって実行時でも使えるようになります。
3. なぜ inline が必要なのか?
reified は インライン関数 とセットでしか使えません。
つまり:
inline fun <reified T> foo() { ... } // ✅ OK
fun <reified T> foo() { ... } // ❌ コンパイルエラー
理由は:
インライン化(inline) によって、呼び出し元に型情報が「埋め込まれる」から。
イメージ図
呼び出し元:
checkType<String>("Hello")
↓ インライン展開後(コンパイル時)
if ("Hello" is String) { ... }
型 T は消去されず、具体的な String 型として展開されます。
これにより実行時にも型チェックが可能になります。
4. reified でできること
| 機能 | 説明 | 例 |
|---|---|---|
| 型チェック |
is T が使える |
if (value is T) |
| クラス参照 |
T::class や T::class.java が使える |
T::class.simpleName |
| リフレクション |
T::class.members などを取得可能 |
Reflection API |
| JSON パースなどの型推論 |
TypeToken の代替 |
Gson/Moshiなど |
5. 例:reified を使った JSON デシリアライズ
通常のジェネリクスでは、型情報が消えるため
List<User> のような複雑な型を扱えません。
reified を使えば次のように書けます:
inline fun <reified T> parseJson(json: String): T {
val type = object : TypeToken<T>() {}.type
return Gson().fromJson(json, type)
}
data class User(val name: String, val age: Int)
val json = """[{"name":"Alice","age":20},{"name":"Bob","age":25}]"""
val users: List<User> = parseJson(json)
println(users)
reified により List<User> の型情報を実行時に渡せるため、
Gson は正しくデコードできます。
6. reified の応用例
① 型判定を共通化
inline fun <reified T> Any?.isTypeOf(): Boolean = this is T
println("abc".isTypeOf<String>()) // true
println(123.isTypeOf<String>()) // false
② クラスインスタンス生成
inline fun <reified T: Any> createInstance(): T =
T::class.java.getDeclaredConstructor().newInstance()
val sb = createInstance<StringBuilder>()
sb.append("Hello Reified!")
println(sb.toString())
③ 複数型の分岐処理
inline fun <reified T> printType(value: T) {
when (T::class) {
String::class -> println("文字列")
Int::class -> println("整数")
else -> println("その他の型")
}
}
printType("Hi") // 文字列
printType(42) // 整数
7. reified の制約まとめ
| 制約 | 内容 |
|---|---|
inline 関数でのみ使える |
コンパイル時展開が必要 |
| 型引数の reification は JVM 上でのみ有効 | JS/Native では動作が異なる場合あり |
| 実行時リフレクションに強いが、メモリ消費に注意 | 型情報を保持するためオーバーヘッドあり |
まとめ
| 項目 | 通常のジェネリクス |
reified ジェネリクス |
|---|---|---|
| 実行時型情報 | 消える(型消去) | 残る(具象化) |
is T チェック |
❌ 不可 | ✅ 可能 |
T::class 参照 |
❌ 不可 | ✅ 可能 |
| 使用制限 | 制限なし |
inline 関数内のみ |
| 主な用途 | 型安全な API 設計 | 実行時に型を使う処理(JSON, DI など) |