Search code examples
kotlingenericsreflectionpolymorphism

Kotlin - Get a List of classes implementing generic interface preserving type visibility


Given this generic interface:

interface Trick<I, T> {
   fun perform(input: I): T
}

I want to get a List of all classes implementing this interface, like this:

fun <I,T>loadTricks(): List<Trick<I, T>>

I already tried to use Google's Auto-Service library but all I was able to get was a list of type List<Trick<*, *>!>, which I think will be a problem as I desired to preserve the visibility on the actual type each implementation uses.

I've also came across the Reflections library, but that one is no longer being actively supported and, as such, I'd like to avoid it.

Maybe I am even going the wrong way about all this, so I'll just explain what I am aiming to achieve:

  • I want a list of all Tricks preserving the information of what Trick each of them is, the input I need to pass to "perform" and what output to expect from it.

Solution

  • As you already mentioned Auto-Service and Reflections, I assume you know how to find the Class or KClass itself. I'll skip that part. Then the only remaining part is how to use Kotlin reflection to get I and T. This is more advanced usage, but it is not that hard neither:

    fun main() {
        val clazz = IntToStringTrick::class
        val baseType = clazz.supertypes.single { it.classifier == Trick::class }
        println("I: ${baseType.arguments[0]}") // Int
        println("T: ${baseType.arguments[1]}") // String
    }
    
    class IntToStringTrick : Trick<Int, String> {
        override fun perform(input: Int) = input.toString()
    }
    

    baseType.arguments[0] returns a KTypeProjection. Depending on your needs, you can acquire the variance from it. The type of I as KClass can be acquired with:

    baseType.arguments[0].type?.classifier as? KClass<*>
    

    You need to be careful though. If the implementation of Trick is itself generic, then the classifier is not a KClass, so the above returns null. There may be other tricky cases, for example documentation mentions star projections and intersection types, but I think both of them are not possible in your case. Anyway, the above code should safely return either KClass or null for any such tricky cases. You could provide additional handling for these cases.