Search code examples
kotlingenericsreflection

How to determine if a class is generic via reflection in Kotlin


Suppose we have the following class:

  data class KeyValuePair<T : Any>(
      val key: String,
      val value: T
  )

If we reflect against the following instance of this class: KeyValuePair("color", "amber"), the first property is of course kotlin.String, but the second property has a type name of T. Is there a programmatic way to determine the this KProperty1 is generic? Except for hacks such as "class name does not contain dots".


Solution

  • You can get the type of a property using:

    val props = KeyValuePair::class.declaredMemberProperties.first { it.name == "value" }
    val returnType = props.returnType
    

    This will give you back an instance of KType that represents T. However, there could potentially be a class T defined somewhere and you wouldn't know if T is the generic type or an actual class. Moreover, the generic type could have any name.

    Given that the T in the member property is the same T defined as the generic type for the class, I think one way of answering your question would involve:

    1. extracting the list of generic types (aka type parameters) of the class
    2. checking if the return type of the property belongs to that list

    Example:

    fun main() {
        val classTypeParams = KeyValuePair::class.typeParameters
    
        KeyValuePair::class.declaredMemberProperties.forEach { prop ->
            val returnType = prop.returnType
            println("${prop.name} -> ${returnType.classifier in classTypeParams}")
        }
    }
    

    which prints:

    key -> false
    value -> true
    

    Things became a bit more complex if you want to do the same for functions because:

    • functions have input and output types, so you need to look in 2 places-
    • functions can defined their own generic type (e.g. fun <T> foo(): T)

    If you want to find out the actual type of T at runtime (e.g. that T is actually a String in your example), you can't do it because of type erasure – you could use reified parameters in inline functions, but even then I don't think you could get what T is at runtime

    EDIT: actually you can get the runtime type of a reified type parameter:

    object Foo {
        inline fun <reified T: Any> foo(obj: T) {
            println(obj::class)
        }
    }
    
    class MyClass
    
    fun main() {
        Foo.foo("some string") // prints kotlin.String
        Foo.foo(123) // prints kotlin.Int
        Foo.foo(MyClass()) // prints MyClass
    }