Kotlin Simplified Generic Methods with Reified Types

s1m0nw1
1,756 views

Open Source Your Knowledge, Become a Contributor

Technology knowledge has to be shared and made accessible for free. Join the movement.

Create Content

Kotlin Reified Types in Inline Functions!

Disclaimer: My articles are published under "Attribution-NonCommercial-NoDerivatives 4.0 International (CC BY-NC-ND 4.0)".

© Copyright: Simon Wirtz, 2017 https://blog.simon-wirtz.de/kotlin-reified-types/

Feel free to share.

Intro

I've noticed that many people haven't ever heard of reified types or have problems understanding what they are and what they do. Therefore this little post is intended to bring some light into the darkness of Kotlin's reified types.

Starting situation

fun <T> myGenericFun(c: Class<T>)

In an ordinary generic function like myGenericFun you can't access the type T because it's, like in Java, erased at runtime and thus only available at compile time.  Therefore, if you want to use the generic type as a normal Class in the function body you need to explicitly pass the class as a parameter like the parameter c in the example. That's correct and works fine but makes it a bit unsightly for the caller.

Inlined function with reified to the rescue

If, on the other hand, you use an inline function with a reified generic type T, the value of T can be accessed even at runtime and thus you don't need to pass the Class<T> in addition. You can work with T as if it was a normal Class, e.g. you might want to check whether a variable is an instance of T, which you can easily do like this: myVar is T.

An inline function with reified type looks like this:

1
2
3
4
5
6
7
8
9
10
11
inline fun <reified T> myGenericFun(a: Any ){
if(a is T){
println("argument matches T")
}else{
println("no match")
}
}
fun main(args: Array<String>) {
myGenericFun<String>(5)
myGenericFun<String>("testString")
}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Be aware, that reified types can only be used in combination with inline functions. Such an inline function makes the compiler copy the function's bytecode into every place where the function is being called (we say the function is being "inlined"). When you call an inline function with reified type, the compiler knows the actual type used as a type argument and modifies the generated bytecode to use the corresponding class directly. Therefore calls like myVar is T become myVar is String (if the type argument were String) in the bytecode and at runtime.

Reified in Action

Let's have a look at an example, where reified is really helpful. We want to create an extension function for String called toKotlinObject, which tries to convert a JSON string to a Kotlin Object, specified by the function's type T. We can use com.fasterxml.jackson.module.kotlin for this and the first approach is the following:

import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper

fun <T : Any> String.toKotlinObject(): T {
    val mapper = jacksonObjectMapper()
    //does not compile!
    return mapper.readValue(this, T::class.java)
}

The readValue function wants us to provide the information which type it's supposed to parse the String to. We try to use the type parameter T and get its Class. This does not work and the compiler tells us: "Cannot use 'T' as reified type parameter. Use a class instead."

Working example without reified

import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import kotlin.reflect.KClass

data class SimpleText(val simple: String)

fun main(args: Array<String>) {
    val asObject = """{"simple": "text"}""".toKotlinObject(SimpleText::class)
    println(asObject)
}

fun <T : Any> String.toKotlinObject(c: KClass<T>): T {
    val mapper = jacksonObjectMapper()
    return mapper.readValue(this, c.java)
}

In a next step we pass the KClass of T explicitly which can directly be used as an argument to readValue. This works and is actually the same approach used in Java code for such scenarios. The function can be called like so:

With reified

Using an inline function with reified type parameter T makes it possible to implement our function as follows:

import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper

data class SimpleText(val simple: String)

fun main(args: Array<String>) {
    val asObject = """{"simple": "text"}""".toKotlinObject<SimpleText>()
    println(asObject)
}

inline fun <reified T> String.toKotlinObject(): T {
    val mapper = jacksonObjectMapper()
    return mapper.readValue(JsonObject(this).encode(), T::class.java)
}

No need to pass the KClass of T additionally, T can be used as if it was an ordinary class. It's important to know, that inline functions with reified types are not callable from Java code, whereas normal inline functions are. That's probably the reason why not every type parameter used in inline functions is reified by default.

Conclusion

This was just a quick introduction to reified types. In my opinion the call to a function with reified types looks way better because we can make use of the <> syntax commonly used whenever generics are relevant. As a result, it's more readable than the Java approach of passing a Class object as a parameter. All the dirty details can be read in this specification document.

If you want to read more about Kotlin's beautiful features I recommend the book Kotlin in Action to you and also like to direct you to my other articles :)

Happy Kotlin coding!

Open Source Your Knowledge: become a Contributor and help others learn. Create New Content