Kotlin - TypeReference<T> Cannot obtain Class<*> For Type Arguments

Viewed 217

I've created a Kotlin equivalent of TypeReference<T> like so:

abstract class TypeReference<T> : Comparable<T> {

    val type: Type get() = getGenericType()
    val arguments: List<Type> get() = getTypeArguments()

    final override fun compareTo(other: T): Int {
        return 0
    }

    private fun getGenericType(): Type {
        val superClass = javaClass.genericSuperclass

        check(superClass !is Class<*>) {
            "TypeReference constructed without actual type information."
        }

        return (superClass as ParameterizedType).actualTypeArguments[0]
    }

    private fun getTypeArguments(): List<Type> {
        val type = getGenericType()
        return if (type is ParameterizedType) {
            type.actualTypeArguments.toList()
        } else emptyList()
    }
}

In order to obtain Class<*> of the generic type and its arguments, I've also created the following extension function (and this is where I believe the problem lies, since this is where the stack trace fails).

fun Type.toClass(): Class<*> = when (this) {
    is ParameterizedType -> rawType.toClass()
    is Class<*> -> this
    else -> Class.forName(typeName)
}

I'm unit testing this like so:

@Test
fun `TypeReference should correctly identify the List of BigDecimal type`() {

    // Arrange
    val expected = List::class.java
    val expectedParameter1 = BigDecimal::class.java
    val typeReference = object : TypeReference<List<BigDecimal>>() {}

    // Act
    val actual = typeReference.type.toClass()
    val actualParameter1 = typeReference.arguments[0].toClass()

    // Assert
    assertEquals(expected, actual)
    assertEquals(expectedParameter1, actualParameter1)
}

The problem I think, lies in the extension function else -> Class.forName(typeName) as it throws:

java.lang.ClassNotFoundException: ? extends java.math.BigDecimal

Is there a better way to obtain the Class<*> of a Type, even when they're generic type parameters?

2 Answers

You need to add is WildcardType -> ... branch to your when-expression to handle types like ? extends java.math.BigDecimal (Kotlin equivalent is out java.math.BigDecimal), ?(Kotlin equivalent is *), ? super Integer(Kotlin equivalent is in java.math.Integer):

fun Type.toClass(): Class<*> = when (this) {
    is ParameterizedType -> rawType.toClass()
    is Class<*> -> this
    is WildcardType -> upperBounds.singleOrNull()?.toClass() ?: Any::class.java
    else -> Class.forName(typeName)
}

Note that in this implementation single upper bound types will be resolved as its upper bound, but all other wildcard types (including multiple upper bounds types) will be resolved as Class<Object>

https://github.com/pluses/ktypes

val typeReference = object : TypeReference<List<BigDecimal>>() {}

val superType = typeReference::class.createType().findSuperType(TypeReference::class)!!

println(superType.arguments.first())// List<java.math.BigDecimal>
println(superType.arguments.first().type?.arguments?.first())// java.math.BigDecimal
Related