Return a concrete class that inherits from a generic class

Hi -

I’m trying to do the following:

 inline fun <reified T> getMapper(klass: Class<T>): RFMFunction<T, Event> =
    when (klass) {
      MClick::class.java -> MClickMapper
      else -> { throw RuntimeException("Unknown event type: $klass") }
    }

with:

object MClickMapper : RFMFunction<MClick, Event>()

but Kotlin doesn’t recognize that MClickMapper is a subclass of RFMFunction<MClick, Event>.

I tried as RFMFunction<MClick, Event> in the when, but that didn’t work.

Is there a way to do that?

Thanks!

Here’s a running example that uses an unchecked cast to fix the return type.

class StringToIntMap: Map<String, Int> by mapOf()

fun <T> getMapType(klass: Class<T>): Map<T, Int> {
    return when (klass) {
        StringToIntMap::class.java -> {
            println("Known class $klass")
            StringToIntMap() as Map<T, Int> // <-- this unchecked cast. 
        }
        else -> { throw RuntimeException("Unknown event type: $klass") }
    }
}
    
fun main() {
    println(getMapType(StringToIntMap::class.java))
    println(getMapType(String::class.java))
}

Notice how we do not use reified and therefore don’t inline the function. The when clause is essentially the same as if (klass == StringToIntMap::class.java) which won’t work for any subclasses of our generic type StringToIntMap–I’d mention that in the functions documentation since callers must provide the exact type they want to match.

There are a few things we can do to change this.

For starters, we could use Kotlin’s reflection to call isInstance or isSubclassOf. The runnable demo should fail since you need to include Kotlin’s reflection as a dependency.

open class StringToIntMap: Map<String, Int> by mapOf()
class ChildMap: StringToIntMap()
//sampleStart
inline fun <reified T> getMapType(): Map<T, Int> {
    return if (T::class.isInstance(StringToIntMap::class)) {
        println("Known class ${T::class}")
        StringToIntMap() as Map<T, Int>
    }
    else throw RuntimeException("Unknown event type: ${T::class}")
}
//sampleEnd
fun main() {
    println(getMapType<StringToIntMap>()) // Exact class
    println(getMapType<ChildMap>()) // Subclass
    println(getMapType<String>()) // Other class
}

^ Note it’s odd to use both reified and take a param of KClass<T> (or Class<T>) since they both serve the same purpose of giving you access to the type. Since you have reified, use T::class instead of taking the redundant param klass.

You could also use Java’s reflection isAssignableFrom. The main downside is that you can’t use it on non-JVM platforms:

open class StringToIntMap: Map<String, Int> by mapOf()
class ChildMap: StringToIntMap()

inline fun <reified T> getMapType(): Map<T, Int> {
    return if (StringToIntMap::class.java.isAssignableFrom(T::class.java)) {
    	println("Known class ${T::class}")
        StringToIntMap() as Map<T, Int>
    } else throw RuntimeException("Unknown event type: ${T::class}")
}

fun main() {
    println(getMapType<StringToIntMap>()) // Exact class
    println(getMapType<ChildMap>()) // Subclass
    println(getMapType<String>()) // Other class
}
1 Like

Awesome! Thank you very much!