Author profile picture

Getting Real with Kotlin's Reified Type Parameters

Let’s think for a moment about everything that you can do with a class name in Kotlin - think of all the cases where you literally type out the name of a class in your source code. I came up with the following 15 cases, but I probably missed a few. Get your scrolling finger ready, here we go…

1. Define a member property:

private val thing: Thing

2. Function argument:

fun doSomething(thing: Thing) {}

3. Type argument:

val list = listOf<Thing>()

4. Type parameter constraint:

class Item<T : Thing>

5. Cast to the type:

something as Thing

6. Define the class:

open class Thing {}

7. Extend the class:

class Other : Thing()

8. Import the class:

import com.example.Thing

9. Reference it in a type alias:

typealias Thingy = Thing

10. Construct an object:

val thing = Thing()

11. Catch an exception type:

catch (e: ExceptionalThing)

12. Invoke a static:

Thing.doStaticStuff()

13. Unbound function reference:

val function = Thing::doSomething

14. Compare types:

something is Thing

15. Assign a Class object:

val clazz = Thing::class.java

Whew, okay… glad we stopped at 15…

The Big Question

Now, the big question is this:

In which of these cases can we use a generic type parameter reference instead of the real class name?

In other words, where can we replace the word Thing above with a type parameter, like T?

Where Can You Reference Type Parameters?

What did you come up with?

Here’s what I found - Cases 1-5 above can all take a type parameter. Let’s demonstrate all five in one go:

class GenericThing<T>(constructorArg: T) {
    // 1. Define a member property
    private val thing: T = constructorArg

    // 2. Define the type of a function argument
    fun doSomething(thing: T) = println(thing)

    // 3. Use as a type argument
    fun emptyList() = listOf<T>()

    // 4. Use as a type parameter constraint, and...
    // 5. Cast to the type (produces "unchecked cast" warning)
    fun <U : T> castIt(): T = thing as U
}

Where Can You Not Reference Type Parameters?

That covers cases 1-5. But for cases 6-15, if you were to substitute a type parameter (e.g., T) everywhere that we’ve got the literal class name (e.g., Thing or ExceptionalThing), you’d end up with a compiler error.

  • In some of these cases, it just wouldn’t make sense to use a type parameter. For example, in case 6 we’re defining a class - what would it even mean to define a class in terms of one of its type parameters?

  • In other cases, Java and Kotlin provide some workarounds via reflection, such as for constructing an object (Case 10).

But in some of these cases, it sure would be nice to be able to use a type parameter. In particular, cases 14 and 15 - comparing types and assigning class objects - would be quite handy in certain situations.

The good news is that reified type parameters allows cases 14 and 15 to compile!

Introducing Reified Type Parameters

Java has limits on what types are considered reifiable - meaning they’re “completely available at run time” (see the Java SE specs on reifiable types). Type parameters are typically erased during compilation, but in the case of reified type parameters in Kotlin, thanks to some fascinating tricks under the hood, you can compare types and get Class objects.

Reified type parameters only work with functions (or extension properties that have a get() function), and only with those functions that are declared to be inline. Here’s an example:

inline fun <reified T> Any.isInstanceOf(): Boolean = this is T

When you mark a function inline, the body of the function is simply “pasted” into the call sites. This is why reified types work - the actual type is known at the call site. So invoking x.isInstanceOf<String>() effectively compiles down to just x is String. (Or in Java terms, that’s x instanceof String).

Our Favorite Use Case

Case 15 above is the one that many Kotlin developers love the most. Let’s say we’ve got a User class, and a JSON string that we want to read from:

data class User(val first: String, val last: String)

val json = """{
      "first": "Sherlock",
      "last": "Holmes"
    }"""

In Java serialization libraries, such as Gson, when you want to deserialize that JSON string, you end up having to pass a Class object along as an argument, so that Gson knows the type you want.

User user = new Gson().fromJson(getJson(), User.class);

Now, let’s whip together a little reified magic - we’ll create a very light extension function to wrap that Gson method:

inline fun <reified T> Gson.fromJson(json: String) = 
        fromJson(json, T::class.java) 

And now, in our Kotlin code, we can deserialize the JSON string, without even having to pass the type information at all!

val user: User = Gson().fromJson(json)

Kotlin inferred the type based on it’s usage - because we assign it to a variable of type User, Kotlin used that as the type argument to fromJson(). Alternatively, you can make the type inference go the other direction:

val user = Gson().fromJson<User>(json)

In this case, the type of user is inferred from the type argument passed to fromJson().

Getting Even More Real

If you’re thirsty for more, check out the concept article about Reified Type Parameters. You can also read the official Kotlin reference on the topic.

Share this article: