Home > Software engineering >  Get type of generic object in Kotlin
Get type of generic object in Kotlin

Time:09-08

I saw many similar topics, but did not find suitable for me.

I have a parent class sealed class Item and a few nested:

data class A(val a: Int) : Item()
data class B(val b: Int) : Item()

There are a lists:

val aList = listOf<A>(...)
val bList = listOf<B>(...)

and some method which take a list as argument:

fun processList(list: List<Item>) {...}

How to get specific type in the processList method?

I know about the way with reified type using:

inline fun <reified T: Item> processList(list: List<T>) {
    val type = T::class
    ...
}

but it cannot work with vararg arguments:

fun processList(vararg items: List<Item>) {...}

which is called for example processList(aList, bList).

Is it possible to get type of list elements without generics? The only idea is get type of first list element, but it is not a very nice solution.

CodePudding user response:

Disclaimer: author of this question explained in comments that they need a way to receive vararg of lists and then get the parameter type of each list separately.

General answer is that this is not possible. Due to type erasure in Java/Kotlin, generic types are not known at runtime. reified introduced by Kotlin is an attempt to mitigate this problem at least partially, although it has its limitations as well. There are some ways to accomplish this, but they're all more like workarounds than real solutions:

1. Do not use vararg, but overload the function.

inline fun <reified T1: Item> processList(list1: List<T1>) {}
inline fun <reified T1: Item, T2: Item> processList(list1: List<T1>, list2: List<T2>) {}
inline fun <reified T1: Item, T2: Item, T3: Item> processList(list1: List<T1>, list2: List<T2>, list3: List<T3>) {}
...

Sounds really bad, but it takes 10 minutes to write such functions for up to 10 params and you do this once. Many libraries do something similar.

You can delegate each above function to a single one that accepts a list/array of KClass/Class, so your real implementation will be in a single place.

2. Check the first item of each list.

Very hacky and it has its limitations. You don't get the compile type, but the runtime type, which depending on your case may be not at all acceptable. It won't work well with heterogeneous collections. Also, if you need to know the type of empty lists, then this is not possible.

3. Create your own implementation of a list which keeps its type.

fun main() {
    val aList = myListOf<A>()
    val bList = myListOf<B>()
    processList(aList, bList)
}

// or:

fun main() {
    val aList = listOf<A>()
    val bList = listOf<B>()
    processList(aList.asMyList(), bList.asMyList())
}

fun processList(vararg items: MyList<Item>) {
    println(items[0].type)
    println(items[1].type)
}

inline fun <reified T : Any> myListOf(vararg items: T): MyList<T> = MyList(T::class, listOf(*items))
inline fun <reified T : Any> List<T>.asMyList(): MyList<T> = MyList(T::class, this)

class MyList<out T : Any> @PublishedApi internal constructor(
    val type: KClass<out T>,
    private val delegate: List<T>
) : List<T> by delegate

Of course, it has the obvious limitation that we have to use our specific list implementation.

  • Related