Reputation: 74909
Given a reified type parameter T
, if that type is a List
, how can I get the list's item type?
I tried overloading the reified method with T : Any
and T : List
and also TItem : Any, TList: List<TItem>
but that doesn't compile due to conflicting overloads.
Fails to compile, conflicting overloads
inline fun <reified T> getRequiredVariable(variableName: String) =
// ... implementation
inline fun <reified TItem, reified TList : List> getRequiredVariable(variableName: String) =
// ... implementation
Failed workaround attempt
inline fun <reified T : Any> getRequiredVariable(variableName: String) =
if (T::class.isSubclassOf(List::class)) {
val tKClass = T::class
val itemType = tKClass.typeParameters[0] // Won't work, even though T is reified, the type parameter apparently is not
val itemClassifier = itemType.createType().classifier
val itemKClass = itemClassifier as KClass<*> // Fails, KTypeParamaterImpl is not KClass<*>
getRequiredListVariable(variableName, itemKClass) as? T
} else {
getRequiredVariable(variableName, T::class)
}
Upvotes: 2
Views: 1241
Reputation: 37710
The problem here is that you want to access type arguments but the KClass
/Class
abstraction only gives access to type parameters (the things declared in the class itself, not the actual types passed in this specific reified call site).
You shouldn't use the class for this, but the Type
/KType
instead. There is a convenient typeOf function to access it on a reified type, and then you can access the type arguments via the arguments property:
val typeArgs = typeOf<T>().arguments
Once you have those, you can access their type and the classifier of that type like you did in your existing code, but this time it will be those of the actual type arguments.
inline fun <reified T : Any> getRequiredVariable(variableName: String) =
if (T::class.isSubclassOf(List::class)) {
val itemTypeProjection = typeOf<T>().arguments[0]
val itemType = itemTypeProjection.type ?: error("Cannot get list item type from List<*>")
val itemKClass = itemType.classifier as? KClass<*> ?: error("The list item type is not denotable in Kotlin")
getRequiredListVariable(variableName, itemKClass) as? T
} else {
getRequiredVariable(variableName, T::class)
}
Note, however, that accessing the type argument 0 will not generally give you the item type for any implementation class of List
. For instance, if the type is class MyStringList : List<String>
, it has no type argument and yet it's a subtype of List
. Even worse, it could have a type argument that has nothing to do with the list elements, as in class MyWeirdClass<X> : List<String>
(using X for something unrelated). To overcome this, you might need to go up the hierarchy of supertypes in order to find the type of the List
interface used in the declaration of T
's type - and this will likely be a pain.
Upvotes: 3