Search code examples
genericskotlinkotlin-reified-type-parameters

How does the reified keyword in Kotlin work?


I'm trying to understand the purpose of the reified keyword. Apparently, it's allowing us to do reflection on generics.

However, when I leave it out, it works just as fine. When does this make an actual difference?


Solution

  • TL;DR: What is reified good for

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

    Inside the body of a generic function such as myGenericFun, it's impossible to access the type T as it's only available at compile time but erased at runtime. 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, as done for myGenericFun above.

    On the other hand, when you create an inline function with a reified T, the type of T can be accessed even at runtime. With that, we don't need to pass the Class<T> additionally. We can work with T as if it was a normal class. For example, if we want to check if a variable is an instance of T, we can: myVar is T.

    An inline function with reified type T as described looks as follows:

    inline fun <reified T> myGenericFun()
    

    How reified works

    We can only use reified combined with an inline function. By doing this, we instruct the compiler to copy the function's bytecode into every spot the function is invoked from (the compiler "inlines" the function). When we call an inline function with reified type, the compiler needs to know the actual type passed as a type argument so that it's able to modify the generated bytecode to use the corresponding class directly. Therefore a call like myVar is T becomes myVar is String in the bytecode (assuming the type argument is String).


    Example

    Let's look at an example demonstrating the benefits of reified.

    We want to create an extension function for String that we call toKotlinObject. It tries to convert a JSON string to a plain Kotlin object with a type specified by the function's generic type T. We use com.fasterxml.jackson.module.kotlin for the conversion and the first approach is the following:

    a) First approach without reified type

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

    The readValue method takes a type to which it is supposed to parse the JsonObject. If we try to get the class (::class.java) of type parameter T, the compiler complains:

    Cannot use 'T' as reified type parameter. Use a class instead.

    b) Workaround with explicit Class parameter

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

    As a workaround, Class of T can be made a method parameter that we pass to readValue. This approach works and is a common pattern in generic Java code. It can be invoked as follows:

    data class MyJsonType(val name: String)
    
    val json = """{"name":"example"}"""
    json.toKotlinObject(MyJsonType::class)
    

    c) The Kotlin way: reified

    Using an inline function with reified type parameter T, we can implement the function more smoothly:

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

    There’s no need to take the Class of T as an additional argument here since T can be used as an ordinary class. For the client, the code looks like this:

    json.toKotlinObject<MyJsonType>()
    

    Important Note: Working with Java

    An inlined function with reified type is not callable from Java code.