Generics are a powerful feature in Kotlin that allow us to write flexible and reusable code. However, one of the key aspects to understand when working with generics is Type Erasure. If you’ve ever tried to inspect a generic type at runtime and found unexpected behavior, you’ve likely encountered type erasure in Kotlin generics.
In this post, we’ll break down Type Erasure in Kotlin Generics, explain why it happens, and how to work around its limitations.
What is Type Erasure in Kotlin Generics?
Type erasure is a process where generic type information is removed (erased) at runtime. This happens because Kotlin, like Java, relies on the JVM, which does not retain generic type parameters during runtime. Instead, all generic types are replaced with their upper bound (often Any?
) when the code is compiled.
This means that while we can specify a generic type at compile time, that type information is lost when the code is running. As a result, certain operations that rely on runtime type checking, such as type comparisons, become problematic.
Let’s first see a simple example to understand type erasure.
fun <T> printType(value: T) {
println("Type: " + value!!::class.simpleName) //Expression in class literal has nullable type 'T'. so used '!!' to make the type non-nullable.
}
fun main() {
printType(42) // Output: Type: Int
printType("Hello") // Output: Type: String
}
Here, this will work well because value!!::class.simpleName
gives us some runtime type information. But the problem arises when we try to inspect a collection of generics.
Consider the following code:
fun <T> isListOfStrings(list: List<T>): Boolean {
return list is List<String>
}
fun main() {
val strings = listOf("Kotlin", "Generics")
println(isListOfStrings(strings))
}
You might expect isListOfStrings(strings)
to return true
, but instead, it won’t compile (CE: Cannot check for instance of erased type ‘kotlin.collections.List<kotlin.String>) because Kotlin prevents direct generic type checks due to type erasure.
At runtime, List<String>
and List<Int>
both become just List<?>
, meaning the type distinction is lost. This is what type erasure does—it removes specific type details.
Why Does Type Erasure Happen?
Kotlin runs on the Java Virtual Machine (JVM), which historically did not support reified generics. Java introduced generics in Java 5, but to maintain backward compatibility, the compiler removes type parameters at runtime. Kotlin inherits this behavior from Java.
For example, both List<String>
and List<Int>
compile down to just List
in bytecode. This allows Java and Kotlin to remain compatible with older Java versions, but at the cost of losing runtime type safety.
How to Work Around Type Erasure
Even though type erasure limits what we can do at runtime, Kotlin provides workarounds to help mitigate these issues.
Use Reified Type Parameters with Inline Functions
One of Kotlin’s best solutions for type erasure is inline functions with reified type parameters. Normally, generic types are erased, but reified types retain their type information at runtime.
inline fun <reified T> isTypeMatch(value: Any): Boolean {
return value is T
}
fun main() {
println(isTypeMatch<String>("Hello")) // true
println(isTypeMatch<Int>("Hello")) // false
}
Since the function is inline, the compiler replaces it with actual type arguments during compilation, preserving type information.
Use Explicit Type Tokens
Another workaround is using explicit type tokens by passing a Class<T>
reference:
fun <T> printType(type: Class<T>) {
println("Type is: ${type.simpleName}")
}
fun main() {
printType(String::class.java) // Output: Type is: String
}
Conclusion
Understanding type erasure in Kotlin generics is essential when working with generics on the JVM. Due to type erasure, generic type information is removed at runtime, leading to certain limitations like the inability to check generic types, create instances, or overload functions based on generic parameters.
However, Kotlin provides solutions like reified type parameters, class references, and type tokens to mitigate these issues. By using these techniques, you can work around type erasure and write more effective, type-safe Kotlin code.