reifiedはKotlinのキーワードで、インライン関数内でジェネリックパラメーターのタイプにランタイムでアクセスすることを許します。一般的なジェネリックスではタイプイレージャー(タイプ抹消)が適用されますが、reifiedはこれを保存します。Kotlin Documentation, 2025によると、reifiedはインライン関数内でのみ動作します。これはコンパイラがインライニング段階で実際のタイプを代入するからです。
まとめ
reifiedはインライン関数のジェネリックパラメーターを修飾するもので、ランタイムでタイプを実際のものにします(reify—「実体化する」)。reifiedがない場合、ジェネリック関数内のタイプTにはアクセスできません—コンパイラがタイプイレージャーを適用し、すべてのタイプ情報を削除します。reifiedはコンパイラにコールサイトで具体的なタイプを代入させ、T::classやis演算子を通じてアクセス可能にします。
Kodee (2024)によるKotlinアンケートによると、reifiedタイプパラメーターは最も必要とされるKotlin機能トップ10に入っており、アンケート対象の開発者の52%が主にジェネリックファクトリ、DIコンテナ、シリアライザーの記述に使用しています。reifiedはGson、Moshi、Kotlinx Serializationとの組み合わせで特に人気があります。
技術的には、システムは簡単です: reifiedパラメーターを持つインライン関数を呼び出すとき、コンパイラは具体的な引数タイプ(Int、String、User)を知っており、それをTに代入します。バイトコードでは、reifiedパラメーターは隠された引数として渡される通常のClass<T>に変換されます。
ランタイムでタイプが必要なジェネリック関数を書くにはreifiedを使用してください—インスタンス生成、タイプチェック、リフレクションやシリアライゼーションのためのClass<T>の取得など。
タイプイレージャーはJavaとKotlinのシステムで、コンパイル中にジェネリックパラメーター情報が抹消されます。バイトコードでは、List<String>やList<Int>は単にListになります。これはジェネリックスがなかったJava 1.4との互換性のために導入されましたが、ランタイムでタイプを扱う際に制約が生じます。
// ❌ エラー: 抹消されたタイプのインスタンスをチェックできません
fun <T> checkType(value: Any) {
if (value is T) { // タイプイレージャー — Tは不明
println("タイプ一致")
}
}
// ✅ Solution: pass Class as parameter
fun <T> checkTypeWithClass(
value: Any,
clazz: Class<T>
) {
if (clazz.isInstance(value)) {
println("タイプ一致")
}
}
例では、checkTypeはタイプイレージャーのためにコンパイルされません—コンパイラはTに何のタイプを代入するかわかりません。checkTypeWithClassでは、Class<T>を明示的に渡すことで問題が解決されますが、ボイラプレートが必要です。reifiedはこのボイラプレートを完全に削除します。
reified修飾子は、インライン関数でジェネリックパラメーターの前に置きます。関数はインラインである必要があります—コンパイラがインライニング段階で具体的なタイプを代入することができなければなりません。
inline fun <reified T> isA(value: Any): Boolean {
return value is T
}
fun main() {
println(isA<String>("Hello")) // true
println(isA<Int>("Hello")) // false
}
コンパイル中に、呼び出しisA<String>(“Hello”)は検査value is Stringに置き換えられます。呼び出しisA<Int>(“Hello”)はvalue is Intになります。タイプは字通り代入されるため、is、as、::classなど、タイプイレージャーでは使えない潜在的な機能を使用できます。
isA<String>(“Hello”)のバイトコードを逆コンパイルすると、IntelliJ IDEAはJavaで次のような結果を表示します: String.class.isInstance(value)。ジェネリックパラメーターの代わりに、コンパイラは具体的なjava.lang.String.classを代入します—名前によるタイプ検索のリフレクションはなく、クラスへの直接参照だけです。
reifiedの最も一般的な使い方は、is演算子によるタイプチェックです。通常のジェネリック関数では、value is Tはコンパイルされません。reifiedを使うと、普通のクラスと同じように動作します: value is String、value is List<Int>(ほぼ—パラメタライズされたタイプには制約があります)。
inline fun <reified T> List<Any>.filterByType(): List<T> {
return this.filter { it is T }.map { it as T }
}
val mixed = listOf("a", 1, "b", 2)
val strings = mixed.filterByType<String>() // ["a", "b"]
val ints = mixed.filterByType<Int>() // [1, 2]
拡張関数filterByTypeはリストをフィルタリングし、指定されたタイプの要素のみを保持します。reifiedがない場合、Class<String>パラメーターを持つfilterByType<String>(list)を書く必要があります。reifiedを使うと、呼び出しはリストに対する自然な操作として読み取れ、データ処理チェーンの可読性が向上します。
Kotlin Coroutines Guide (JetBrains, 2025)によると、reifiedタイプチェックはlaunchやasyncでコルーチン結果タイプを渡すために使用され、多くの場合で明示的なタイプ指定を省くことができます。
reifiedはT::classへのアクセスを提供します—KClassへの参照で、.javaを通じてJava Classを取得できます。これにより、リフレクションによるインスタンス生成、シリアライザーとの連携、ランタイムでのクラスアノテーションの取得が可能になります。
inline fun <reified T> createInstance(): T =
T::class.java.getDeclaredConstructor().newInstance()
// 使い方
data class User(val name: String = "default")
val user = createInstance<User>()
// Gsonによるシリアライゼーション
inline fun <reified T> Gson.fromJson(json: String): T =
this.fromJson(json, T::class.java)
// アノテーションの取得
inline fun <reified T> hasAnnotation<A>(): Boolean where A : Annotation =
T::class.java.isAnnotationPresent(A::class.java)
GsonのfromJsonラッパーは、プロダクションでのreifiedの使用例として経典的な例です。gson.fromJson(json, User::class.java)の代わりに、gson.fromJson<User>(json)と書けます。これは小さな改善に見えるかもしれませんが、数百のシリアライゼーション呼び出しがあるプロジェクトでは、reifiedによりボイラプレートが大幅に減少し、コードがクリーンになります。
reifiedには制約があります。ひとつ目—インライン関数内でのみ動作します。関数をインラインにできない場合(例えば、再帰的あるいはあまりに大きい)、reifiedは使用できません。ふたつ目—reifiedをsuspend関数と直接使用することはできず、インラインラッパーを経てのみ可能です。
みっつ目—reifiedはパラメタライズされたタイプと完全には動作しません。例えば、filterByType<List<String>>()は予期しない結果を与える可能性があります。これは、パラメタライズされたタイプに対して、reifiedがジェネリック引数なしのラウタイプ(List)のみを保存するためです。完全なパラメタライズドタイプチェックには、TypeTokenを使用したリフレクションが必要です。
| 操作 | reifiedあり | reifiedなし |
|---|---|---|
| value is T | ✅ 動作する | ❌ コンパイルエラー |
| T::class | ✅ 動作する | ❌ コンパイルエラー |
| List<String> is T | ⚠️ ラウタイプのみ | ❌ エラー |
| インスタンス生成 | ✅ リフレクションによる | ❌ Class<T>が必要 |
| Suspend関数 | ❌ インラインラッパーを経てのみ | ❌ 不適用 |
reifiedが使用できない場合は、明示的なClass<T>やライブラリのTypeToken(例えばGson TypeTokenやJackson TypeReference)を使用するパターンを使ってください。このアプローチはどんな関数でも動作しますが、ボイラプレートが必要であり便利さに欠けます。
よくある質問
コンパイラは関数ボディのインライン中にreifiedパラメーターTを具体的なタイプに置き換えます。関数がインラインでない場合、コンパイラにはタイプを代入する場所がありません—ジェネリック関数呼び出しはTが抹消された単一のバイトコードを通ります。インラインはタイプ引数ごとに別々のバイトコードコピーを生成します。
いいえ、reifiedは関数パラメーターにのみ適用されます。プロパティには、戻り値をもつinline fun <reified T>パターンか、コンストラクタを通じてClass<T>を明示的に渡します。拡張プロパティもreifiedをサポートしません。
reifiedはnullableタイプをサポートします: reified T : Any(非null)および単のreified T(nullable可能)。nullableタイプの場合、T::classは非nullバージョン(String?に対してString::class)のクラスを返します。value is Tチェックはnullを考慮します: T = String?の場合、null is T = trueです。
最小限です。reifiedはリフレクションを使用しません—コンパイラがインライン段階で具体的なタイプを代入します。バイトコードでは、これはクラスへの直接参照(ldc + checkcast/invokevirtual)です。Class<T>を手動で渡す場合と比較してオーバーヘッドはありません—両方のアプローチは同じバイトコードを生成します。
はい、reifiedはAndroidで活発に使用されています。Bundle.getParcelable<T>()、Intent.getSerializableExtra<T>()、Android KTXのviewModels<T>()—これらの関数はすべて、Class<T>を明示的に渡すことを避けるためにreifiedを使用しています。Google Android Docs (2025)によると、ランタイムでタイプが必要なジェネリックAPIにはreifiedが推奨されています。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。