loading...

Kotlin Reified Generics: Explained

cjbrooks12 profile image Casey Brooks ・2 min read

Kotlin is a wonderful language full of many little goodies and surprises that make day-to-day development so much more pleasant. Most of its goodies are really just a very smart compiler that does for you much of what you would have done anyway, and reified generics are a great example of that.

If you're sitting there thinking "what the 🙊 are reified generics, and when should I use them?", then this post is for you. The following is an answer I posted in the Kotlin Slack to that exact question, and figured it might help people out here as well.

In normal Java generics, the generic parameter is only known at compile time, and is erased from the runtime. Kotlin’s reified generics allow you to pretend that the generic is still there at runtime, by effectively swapping out the placeholder <T> with the known type when it is actually used.

For example:

class JavaClass {
    <T> List<T> getList() { ... }
}
// somewhere else
List<String> list = new JavaClass().getList<String>();

effectively compiles in Java to

class JavaClass {
    List getList() { ... }
}
// somewhere else
List list = new JavaClass().getList();

The assumption here is that if the type-checker is able to verify at compile-time that everywhere that T is used, it is safe, then at runtime all calls to that method are safe and that T is not really needed any longer. But there are times that it would be nice to know that T parameter at runtime, which is what reified generics allows for.

An example where you might want to have that T at runtime is getting a value from a map, where the map can hold any type, and your class needs to check the type of the object in the map.

class JavaClass {
    <T> T getItemFromMap(String key) { 
        Object item = _map.get(key); // this is OK
        if(item instanceof T) { // compilation error, T is not known here at runtime
            return (T) item
        }
        else {
            return null;
        }
    }
}

The solution is to pass a Class object matching the T parameter, which has methods to do that check for you:

class JavaClass {
    <T> T getItemFromMap(Class<T> itemClass, String key) { 
        Object item = _map.get(key); // this is OK
        if(itemClass.isAssignableFrom(item.getClass())) { // this works
            return (T) item
        }
        else {
            return null;
        }
    }
}

But now our method is ugly, and we have to pass 2 parameters to it to get it to work, when at compile-time all the information is readily available. Reified generics make it possible to get around this restriction. The JVM fundamentally does not have the ability to make this work at runtime, so reified generics use a trick at compile-time to make it seem like that: it just inlines the function call and replaces the generic parameter with the actual Class in that inlined code.

Going back to the map example, we can rewrite it using reified generics like this:

inline fun <reified T> getItemFromMap(String key) { 
    val item = _map.get(key)
    if(T::class.java.isAssignableFrom(item.getClass())) {
        return (T) item
    }
    else {
        return null;
    }
}
// somewhere else
val list: String = getItemFromMap("key")

effectively compiles to

val item = _map.get(key)
val list: String = if(String::class.java.isAssignableFrom(item.getClass())) {
    (String) item
}
else {
    null;
}

I hope this was helpful!

Posted on by:

cjbrooks12 profile

Casey Brooks

@cjbrooks12

What's the best way to get something production-ready? Use it in production.

Discussion

markdown guide
 

Thank you! This is a fantastic explanation of reified generics and their main use case!

 

So I tried a few of these examples - they are interesting in theory but they don't compile...

 

This is something I really miss in Java...

 

This would be really useful if it had a working code snippet. I went elsewhere to find some simple sample and now I have a better idea.