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.
But 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).
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.
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>