0

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?

HJCee
  • 163
  • 2
  • 6

2 Answers2

1

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
ephemient
  • 198,619
  • 38
  • 280
  • 391
0

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()
}
PHaroZ
  • 23
  • 1
  • 4
  • I considered something similar to this (i.e. a factory function) but unfortunately my repository is an abstract class as I want the ability to add functionality to the repositories as required. I couldn't figure out a way to make this work using this pattern. – HJCee Sep 21 '20 at 14:05