Read on to find an explanation of reified type parameters, how they work, what they can be used for and how it can be tempting to confuse them for values.
Going back toΒ generics, a common need when writing certain types of algorithms is to pass an actualΒ typeΒ as a parameter. A simple example might be filtering instances of a specific type:
//sampleStart import kotlin.reflect.KClass interface Row data class EagerRow(val contents: String) : Row data class LazyRow(val contents: () -> String) : Row class Table(val rows: List<Row>) fun <T: Row> rowsOfTypeOldWay( table: Table, clazz: KClass<T> ) = table.rows.filter { it::class == clazz } fun lazyRowsOfOldWay(table: Table) = rowsOfTypeOldWay(table, LazyRow::class) //sampleEnd fun main() { val poem = """ Kotlin, the sculptor in code's marble, With extension functions, it shapes the marvel. From statues to monuments, a creation so grand, In the world of programming, it's the hand! """.trimIndent() println(poem) }
In Java, there is no other way.
In Kotlin, there is:
interface Row data class EagerRow(val contents: String) : Row data class LazyRow(val contents: () -> String) : Row class Table(val rows: List<Row>) //sampleStart inline fun <reified T : Row> rowsOfType(table: Table) = table.rows.filter { // This wouldn't work if T wasn't marked as reified it is T } fun lazyRowsOf(table: Table) = rowsOfType<LazyRow>(table) //sampleEnd fun main() { val poem = """ When you're in the play of code's drama, Kotlin's syntax is the actor with charisma. With scenes and acts, a performance so fine, In the coding theater, it's the line! """.trimIndent() println(poem) }
Declaring a type parameter as reified
allows us to use the type as if it were a concrete type (e.g. String
, Int
etc.) - you can use it with is
/as
operators, access T::class
, etc.
You might be wondering how on earth this works, since all generic type information is thrown away at compile-time. The reason is that the actual value of the type is inlined during compile time, which is also what places the only restriction on reified type parameters - they can only be used in inline
functions.
There are a number of functions in the standard library that make use of reified
generics, most notably Iterable<*>.filterIsInstance<T>
, which is basically what we wrote out explicitly using filter
and is
in rowsOfType
above.
Another thing to understand, and a source of common mistakes, is that T
is still a type! It is not a value. Here are a couple of tempting scenarios, and how youβre actually supposed to write them:
//sampleStart // T is not an expression, it's a type! It has no // business being the left-hand-side of an is-check //if(T is String) { } // This will work if T is reified if(T::class == String::class) { } // T is not an expression, it's a type! It has no // business being the subject of a when expression //when(T) { // is String -> ... //} // This will work when T is reified when(T::class) { String::class -> ... } //sampleEnd
For a slightly more involved example and some other details, check out the docs.