455

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?

3
  • 25
    Generic type parameters are erased at runtime, read about type erasure if you haven't already. Reified type parameters on inline functions not only inline the method body, but also the generic type parameter allowing you do to things like T::class.java (which you can't do with normal generic types). Putting as a comment because I don't have time to flesh out a full answer right now.. Commented Aug 29, 2017 at 23:34
  • 2
    It allows to get access to the concrete generic type of a function without relying on reflection and without having to pass the type as argument. Commented Aug 30, 2017 at 1:35
  • 1
    You lost me at erasure. I know it's tradition in kotlin to define keywords via other keywords, but it doesn't help new-comers who don't know what the other words mean either. Commented Dec 29, 2022 at 14:32

4 Answers 4

937

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.

Sign up to request clarification or add additional context in comments.

7 Comments

Thanks for your comprehensive response! That actually makes sense. Just one thing I'm wondering, why is reified needed if the function is being inlined anyway? It would leave the type erasure and inline the function anyway? This seems kind of a waste to me, if you inline the function you might aswell inline the type being used or am I seeing something wrong here?
Thanks for your feedback, actually I forget to mention something which might give you the answer: a normal inline function can be called from Java but one with a reified type parameter can't! I think this is a reason why not every type parameter of an inline function is automatically made reified.
What if the function is a mix of reified and non-reified parameters? That makes it not eligible to be called from Java anyways, why not reify all type parameters automatically? Why does kotlin need to have reified specified for all type parameters explicitly?
what if upper callers in the stack need not json.toKotlinObject<MyJsonType>(), but json.toKotlinObject<T>() for different objects?
By the way, I have learnt ObjectMapper are expensive to construct so should not construct one new whenever wanting to deserialize one object. Thus, is there an approach to make use of reified for beautiful code and reuse objectmapper across deserializing? thanks
|
198

Understanding reified types

Generics

While using generics in Kotlin, we can perform operations on a value of any type T:

fun <T> doSomething(value: T) { println("Doing something with value: $value") // OK } 

Here we are implicitly calling the toString() function of the value and that works.

But we can't perform any operations on the type T directly:

fun <T> doSomething(value: T) { println("Doing something with type: ${T::class.simpleName}") // Error } 

Let's understand the reason for this error.

Type erasure

In the code above, the compiler gives an error: Cannot use 'T' as reified type parameter. Use a class instead. This happens because at compile time, the compiler removes the type argument from the function call.

For example, if you call the function as:

doSomething<String>("Some String") 

The compiler removes the type argument part <String> and all that's left at the runtime is:

doSomething("Some String") 

This is called type erasure. So, at runtime (inside the function definition), we cannot possibly know exactly which type the T stands for.

Java solution

The solution to this type erasure problem in Java was to pass an additional argument specifying the type with the Class (in Java) or KClass (in Kotlin):

fun <T: Any> doSomething(value: T, type: KClass<T>) { println("Doing something with type: ${type.simpleName}") // OK } 

This way our code is not affected by type erasure. But this solution is verbose and not very elegant since we have to declare it as well as call it with an additional argument. Also, specifying the type bound Any is mandatory.

Type reification

The best solution to the problem above is type reification in Kotlin. The reified modifier before the type parameter enables the type information to be retained at runtime:

inline fun <reified T> doSomething(value: T) { println("Doing something with type: ${T::class.simpleName}") // OK } 

In the code above, thanks to the reified type parameter, we no longer get the error while performing an operation on type T. Let's see how inline functions make this magic possible.

inline functions

When we mark a function as inline, the compiler copies the actual body of that inline function wherever that function is called. Since we marked our doSomething() function as an inline, the following code:

fun main() { doSomething<String>("Some String") } 

gets compiled to:

fun main() { println("Doing something with type: ${String::class.simpleName}") } 

So, the two code snippets shown above are equivalent.

While copying the body of an inline function, the compiler also replaces the type parameter T with the actual type argument that is specified or inferred in the function call. For example, notice how the type parameter T is replaced with the actual type argument String.


Type checking and type casting of reified types

The main objective of a reified type parameter is to know the exact type that the type parameter T represents at runtime.

Let's say we have a list of different types of fruits:

val fruits = listOf(Apple(), Orange(), Banana(), Orange()) 

And we want to filter all the Orange types in a separate list like following:

val oranges = listOf(Orange(), Orange()) 

Without reified

For filtering the fruit types, we may write an extension function on List<Any> like following:

fun <T> List<Any>.filterFruit(): List<T> { return this.filter { it is T }.map { it as T } // Error and Warning } 

In this code, first we filter the types and only take the element if its type matches the given type argument. Then we cast each element to the given type argument and return the List. But there are two problems.

Type checking

While type checking it is T, we are introduced to another error by the compiler: Cannot check for instance of erased type: T. This is another kind of error you may come across due to type erasure.

Type casting

While type casting it as T, we are also given a warning: Unchecked cast: Any to T. The compiler cannot confirm the type due to type erasure.

reified types to the rescue

We can easily overcome these two problems by marking the function as inline and making the type parameter reified as explained earlier:

inline fun <reified T> List<Any>.filterFruit(): List<T> { return this.filter { it is T }.map { it as T } } 

And then call it like following:

val oranges = fruits.filterFruit<Orange>() 

I showed this function for easier demonstration. For the purpose of filtering the types in collections, there is already a standard library function filterIsInstance(). This function has used the inline and reified modifiers in the similar manner. You can simply call it as following:

val oranges = fruits.filterIsInstance<Orange>() 

Passing reified parameter as an argument

The reified modifier makes it possible for a function to pass the type parameter as a type argument to another function that has reified modifier:

inline fun <reified T> doSomething() { // Passing T as an argument to another function doSomethingElse<T>() } inline fun <reified T> doSomethingElse() { } 

Getting the generic type of the reified type

Sometimes a type argument can be a generic type. For example, List<String> in the function call doSomething<List<String>>(). It's possible to know this entire type, thanks to reification:

inline fun <reified T> getGenericType() { val type: KType = typeOf<T>() println(type) } 

Here the typeOf() is a standard library function. The println() function above will print kotlin.collections.List<kotlin.String>, if you call the function as getGenericType<List<String>>(). The KType includes KClass, type argument information and nullability information. Once you know the KType, you can perform reflection on it.


Java interoperability

The inline functions declared without reified type parameters can be called from Java as regular Java functions. But the ones declared with the reified type parameters are not callable from Java.

Even if you call it using the reflection like following:

Method method = YourFilenameKt.class.getDeclaredMethod("doSomething", Object.class); method.invoke("hello", Object.class); 

You get the UnsupportedOperationException: This function has a reified type parameter and thus can only be inlined at compilation time, not called directly.


Conclusion

In many cases, the reified types help us get rid of the following errors and warnings:

  1. Error: Cannot use 'T' as reified type parameter. Use a class instead.
  2. Error: Cannot check for instance of erased type: T
  3. Warning: Unchecked cast: SomeType to T

6 Comments

this is way more explicit than the accepted answer, thank you!
Excellent explanation. Thank You. But why type is erased?
@VengateshMurugasamy, if the generic types were to be retained, they would consume a lot of memory and other resources in JVM. So, it was a design decision taken by JVM designers to erase the generic types at runtime. They are useful at compile time for type safety.
Not sure about other answers are clear enough in explanation. But Im sure I understood reified type after reading the above explanation. Thanks @YogeshUmeshVaity
Thank you very much for this AMAZING explanation! It is really clear and to the point ;)
|
16

The purpose of reified is to allow the function to use T at compile time (to access it within the function).

For example:

inline fun <reified T:Any> String.convertToObject(): T{ val gson = Gson() return gson.fromJson(this,T::class.java) } 

To use:

val jsonStringResponse = "{"name":"bruno" , "age":"14" , "world":"mars"}" val userObject = jsonStringResponse.convertToObject<User>() println(userObject.name) 

Comments

-1

If you need the type of your generic parameter in your function, you have to make it reified.

Since the compiler has to replace the generic type to get its type, it will also ask you for adding inline keyword.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.