Reified Keyword in Kotlin: Simplify Your Generic Functions
Kotlin’s reified
keyword lets your generic functions know the actual type used at runtime, dodging the common issue of type erasure found in Java and standard Kotlin generics. This means you can check and cast types inside generic functions without extra workarounds.
The Problem: Type Erasure in Generics
Ever written a generic function in Kotlin (or Java) and tried to find out what T
actually is inside that function? You probably hit a wall. Normally, when your code compiles, the specific type information for generics (like <String>
or <Int>
) gets wiped out. This is called type erasure. At runtime, your function just sees a plain Object
(or its upper bound), not the specific type you called it with.
This means code like this won’t work:
fun <T> printType(value: T) {
<em>// Error: Cannot use 'T' as reified type parameter. Use a class instead.</em>
println("The type is: ${T::class.simpleName}")
}
Why? Because T
doesn’t exist as a specific type at runtime due to erasure. The old way around this was to pass the class type explicitly, which felt a bit clunky:
fun <T: Any> printTypeManual(value: T, type: kotlin.reflect.KClass<T>) {
println("The type is: ${type.simpleName}")
}
<em>// Calling it:</em>
printTypeManual("Hello", String::class) <em>// Output: The type is: String</em>
It works, but it’s extra typing we’d rather avoid.
The Solution: inline
+ reified
Kotlin offers a much cleaner way: combine the inline
and reified
keywords.
inline
: This tells the compiler to copy the function’s code directly into the place where it’s called, instead of making an actual function callreified
: When used on a type parameterT
within aninline
function, it tells the compiler not to erase the type information forT
. The actual type used at the call site gets baked into the inlined code
Let’s fix our previous example:
inline fun <reified T> printTypeReified(value: T) {
<em>// Now this works!</em>
println("The type is: ${T::class.simpleName}")
}
<em>// Calling it is simpler:</em>
printTypeReified("Hello Kotlin!") <em>// Output: The type is: String</em>
printTypeReified(123) <em>// Output: The type is: Int</em>
See? No need to pass String::class
or Int::class
. The compiler handles it because the function is inlined and the type is reified.
Practical Use Cases
Okay, printing the type name is neat, but where does reified
really shine?
1. Type Checking and Casting (is
and as
)
This is a big one. Normally, you can’t do value is T
inside a generic function because T
is erased. With reified
, you can!
Imagine filtering a list containing different object types:
<em>// Assume these classes exist: Apple, Orange, Banana</em>
val mixedFruitBasket = listOf(Apple(), Orange(), Banana(), Orange())
<em>// Generic filter without reified (doesn't work well)</em>
<em>// fun <T> List<Any>.filterType(): List<T> { </em>
<em>// // Error: Cannot check for instance of erased type: T</em>
<em>// return this.filter { it is T }.map { it as T } </em>
<em>// }</em>
<em>// With reified:</em>
inline fun <reified T> List<Any>.filterTypeReified(): List<T> {
val result = mutableListOf<T>()
for (item in this) {
if (item is T) { <em>// Type check works!</em>
result.add(item) <em>// Smart cast often works too, but explicit 'as T' might be needed sometimes</em>
}
}
return result
}
<em>// Usage:</em>
val orangesOnly = mixedFruitBasket.filterTypeReified<Orange>()
println(orangesOnly) <em>// Output: [Orange@..., Orange@...]</em>
<em>// Note: Kotlin's standard library already has filterIsInstance<T>() which does this!</em>
val bananasOnly = mixedFruitBasket.filterIsInstance<Banana>()
println(bananasOnly) <em>// Output: [Banana@...]</em>
reified
makes type checks (is T
) and safe casts (as? T
) possible within generic inline functions
2. Working with APIs Expecting Class Types
Many APIs, especially in Android development (like starting Activities or working with Fragments), require you to pass a Class
object (YourActivity::class.java
). reified
makes creating helper functions for these scenarios much nicer.
import android.content.Context
import android.content.Intent
import android.app.Activity
<em>// Without reified</em>
fun <T : Activity> Context.startActivityManual(activityClass: Class<T>) {
val intent = Intent(this, activityClass)
startActivity(intent)
}
<em>// With reified</em>
inline fun <reified T : Activity> Context.startActivityReified() {
val intent = Intent(this, T::class.java) <em>// Accessing .java works!</em>
startActivity(intent)
}
<em>// Usage (inside an Activity or Context):</em>
<em>// startActivityManual(MyOtherActivity::class.java) // Old way</em>
startActivityReified<MyOtherActivity>() <em>// Much cleaner!</em>
This pattern is super common for simplifying Android Intent creation, Fragment instantiation, database operations, JSON parsing like with libraries such as Gson or Moshi, and dependency injection frameworks.
Quick Summary & Limitations
- What it does:
reified
(used withinline
) preserves generic type information at runtime. - Why use it: Allows type checks (
is T
), casts (as T
), and accessing class info (T::class.java
) inside generic functions. - Key Benefit: Reduces boilerplate code, especially when interacting with Java APIs or performing type-specific logic in generics.
- Limitation: Only works with
inline
functions. Cannot be used on class properties or non-inline function parameters.
So next time you’re fighting type erasure in your Kotlin generics, remember the inline
and reified
combo – it might just be the clean solution you need!
The reified
keyword is a neat tool in your Kotlin toolbox, especially when you’re wrestling with generic type information at runtime. By pairing it with inline
functions, you can write cleaner, more readable, and more powerful generic code, sidestepping the usual limitations of type erasure. Just remember it’s specifically for inline
functions!
Want to know more about Kotlin generics in general? Check out this guide on generics in Kotlin. You might also find understanding Kotlin Companion Objects helpful for related concepts.