Instantiating classes from non-reified type parameters

471 Views Asked by At

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?

2

There are 2 best solutions below

0
ephemient On BEST ANSWER

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
1
PHaroZ On

In your own CrudRepository you can add a companion object with an inline fun which is responsible to instantiate your repository by passing to it the corresponding class.

class MyCrudRepository<T> protected constructor(
        private val type: Class<T>,
) {
    companion object {
        inline fun <reified T : Any> of() = MyCrudRepository(T::class.java)
    }

    fun createTypeInstance() = type::class.createInstance()
}