Search code examples
kotlingenericsormspring-datajasync-sql

Instantiating classes from non-reified type parameters


I'm building an ORM for use with jasync-sql in Kotlin and there's a fundamental problem that I can't solve. I think it boils down to:

How can one instantiate an instance of a class of type T, given a non-reified type parameter T?

The well known Spring Data project manages this and you can see it in their CrudRepository<T, ID> interface that is parameterised with a type parameter T and exposes methods that return instances of type T. I've had a look through the source without much success but somewhere it must be able to instantiate a class of type T at runtime, despite the fact that T is being erased.

When I look at my own AbstractRepository<T> abstract class, I can't work out how to get a reference to the constructor of T as it requires accessing T::class.constructors which understandably fails unless T is a reified type. Given that one can only used reified types in the parameters of inline functions, I'm a bit lost as to how this can work?


Solution

  • On the JVM, runtime types of objects are erased, but generic types on classes aren't. So if you're working with concrete specializations, you can use reflection to retrieve the type parameter:

    import java.lang.reflect.*
    ​
    abstract class AbstractRepository<T>
    ​
    @Suppress("UNCHECKED_CAST")
    fun <T> Class<out AbstractRepository<T>>.repositoryType(): Class<T> =
        generateSequence<Type>(this) {
            (it as? Class<*> ?: (it as? ParameterizedType)?.rawType as? Class<*>)
                ?.genericSuperclass
        }
            .filterIsInstance<ParameterizedType>()
            .first { it.rawType == AbstractRepository::class.java }
            .actualTypeArguments
            .single() as Class<T>
    ​
    class IntRepository : AbstractRepository<Int>()
    class StringRepository : AbstractRepository<String>()
    interface Foo
    class FooRepository : AbstractRepository<Foo>()
    class Bar
    class BarRepository : AbstractRepository<Bar>()
    ​
    fun main() {
        println(IntRepository::class.java.repositoryType())
        println(StringRepository::class.java.repositoryType())
        println(FooRepository::class.java.repositoryType())
        println(BarRepository::class.java.repositoryType())
    }
    
    class java.lang.Integer
    class java.lang.String
    interface Foo
    class Bar