31

Using reified type parameters, one can write an inline function which works with the type parameter through reflection at runtime:

inline fun <reified T: Any> f() {
    val clazz = T::class
    // ...
}

But when f is called with a parameter which is itself a generic class, there seems to be no way to obtain its actual type arguments through T::class:

f<List<Integer>>() // T::class is just kotlin.collections.List

Is there a way to get actual type arguments of a reified generic through reflection?

hotkey
  • 140,743
  • 39
  • 371
  • 326

2 Answers2

46

Due to type erasure, actual generic arguments cannot be obtained through T::class token of a generic class. Different objects of a class must have the same class token, that's why it cannot contain actual generic arguments.


Edit: Since Kotlin 1.3.50, following the technique described below to get type information for a reified type parameter is no longer necessary. Instead, you can use typeOf<T>() on reified type parameters. This function is a compiler intrinsic, and the compiler processes its call sites by emitting the code that builds a representation of the type as a KType at runtime. The type, therefore, must be known at compile time, which is ensure by the type parameter being reified.


There is a techinque called super type tokens which can give actual type arguments in case when the type is known at compile time (it is true for reified generics in Kotlin because of inlining).

The trick is that the compiler retains actual type arguments for a non-generic class derived from a generic class (all its instances will have the same arguments, good explanation here). They are accessible through clazz.genericSuperClass.actualTypeArguments of a Class<*> instance.

Given all that, you can write a util class like this:

abstract class TypeReference<T> : Comparable<TypeReference<T>> {
    val type: Type = 
        (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0]

    override fun compareTo(other: TypeReference<T>) = 0
}

Explained in Jackson TypeReference which uses the same approach. Jackson Kotlin module uses it on reified generics.

After that, in an inline function with a reified generic, TypeReference needs to be subclassed (an object expression will go), and then its type can be used.

Example:

inline fun <reified T: Any> printGenerics() {
    val type = object : TypeReference<T>() {}.type
    if (type is ParameterizedType)
        type.actualTypeArguments.forEach { println(it.typeName) }
}

printGenerics<HashMap<Int, List<String>>>():

java.lang.Integer
java.util.List<? extends java.lang.String>
hotkey
  • 140,743
  • 39
  • 371
  • 326
  • This only works in Kotlin/JVM, is there a way to make it work in Kotlin/common for multiplatform projects? – CLOVIS Apr 10 '19 at 05:15
  • I feel like I saw a YouTrack issue for the stdlib (or maybe reflect library) to support type tokens directly, but I can't find it now. – mkobit Apr 24 '19 at 14:12
  • 1
    @mkobit This one I believe: https://youtrack.jetbrains.com/issue/KT-28230 – hotkey Apr 24 '19 at 15:55
  • What if I need one of the concrete types of the HashMap in the typing of the return value? So for your example if `printGenerics` would need to have the type of the key, in this case int, as the return type? – Splitframe Jan 23 '23 at 11:31
-1

If you are using gson library already in the project(for example json parsing). It provides a class for this. com.google.gson.reflect.TypeToken(). So you could use something like,

 f(object : TypeToken<List<Integer>>() {}.type)

Shivanand Darur
  • 3,158
  • 1
  • 26
  • 32