Reflection of generic types

Simple example:

class Test {
        data class Q(val t: Array<Int>)
        @Test
        internal fun name() {
            Q::class.primaryConstructor!!.call(Array(10) { it as Number })
        }
    }

Throws an exception: argument type mismatch.

A specific array type is known only at runtime. At compile time, only its superclass is known… In the example, this is Number. How can an instance of class Q be created through reflection?
thanks

There are 2 key differences between arrays and normal generics:

  • Unlike normal Java generics where types are erased at compile time arrays retain generic information in runtime. That’s why you get IllegalArgumentException, because you are passing instance of Number[] in place of Integer[] argument.
  • They are covariant. This was somewhat fixed on Kotlin’s compiler level to make arrays invariant but even so causes a slight performance penalty on JVM and can cause ArrayStoreException. Example:
val intArray: Array<Int> = arrayOf(1, 2, 3)
val unknownArray: Array<*> = intArray // an additional step to force Kotlin's compiler to lose generic information
@Suppress("UNCHECKED_CAST")
val stringArray: Array<Any> = unknownArray as Array<Any> // array is covariant on JVM so it works
stringArray[0] = "1" // throws ArrayStoreException
1 Like

Thanks for the answer. But this does not work in my case :frowning:

data class Q(val t: Array<Int>)

    @Test
    internal fun name() {
        val array: Array<*> = Array(10) { it as Number }
        Q::class.primaryConstructor!!.call(array)
    }

Maybe I misunderstood you and you meant that there is no solution?

I’m not exactly sure of what exactly are you asking.
This class can be instantiated reflectively as long as you pass an Array<Int> as argument, for example:

class Test {
        data class Q(val t: Array<Int>)
        @Test
        internal fun name() {
            Q::class.primaryConstructor!!.call(Array(10) { it  })
        }
    }

I don’t know why you had a cast to Number. That won’t work for the reason given by @madmax1028

If you want a type-safe way to create an object then maybe a better choice would be a constructor reference?

val constructorReference: (t: Array<Int>) -> Q = ::Q
val q = constructorReference(Array(10) { it as Number }) // won't compile

I gave a very simplified example. in fact, things are much more complicated. we can take json deserialization as an example. how to deserialize a field that is of type Array <Int>?

Java code solving this problem:
int[] intArray = (int[]) Array.newInstance(int.class, 1);

Serialization/mapping frameworks can read generic type information from field declarations. As for the object creation they usually contain a preregistered set of converters for primitives, arrays and collections. Other classes either come under general serialization rules or you need to declare them explicitly.

Maybe there is a better way, but this snippet should work:

for (memberProperty in Q::class.memberProperties) {
	val propertyType = memberProperty.returnType.javaType as Class<*>
	if (propertyType.isArray) {
		val array = java.lang.reflect.Array.newInstance(propertyType.componentType, 10) as Array<*>
	}
}
1 Like

It works. thanks