Search code examples
kotlingenericstype-erasure

How to prevent type erasure in Kotlin?


Could you please suggest how to make it work in Kotlin?

interface SomeHandler 
interface Handler<T> : SomeHandler {
    fun handle(e: T)
}

class Sender(vararg val handler: SomeHandler) {
    fun <T> send(e: T) {
        for (h in handler)
            if (h is Handler<T>)
                h.handle(e) // ERROR: Cannot check for instance 
                            // of erased type: Scratch.Handler<T>
    }
}

I just would like to invoke all matching handlers here.


Solution

  • You can't do it that way, I'm afraid.  At runtime, there is no Handler<T> — only Handler.  (The type T has been erased.)  So the only way to make that sort of check is to determine T some other way.

    For example, you could store the type as an extra field in Handler, e.g.:

    interface Handler<T : Any> : SomeHandler {
        val type: KClass<T>
        fun handle(e: T)
    }
    
    class Sender(vararg val handler: SomeHandler) {
        fun <T : Any> send(e: T) {
            for (h in handler)
                if (h is Handler<*> && h.type.isInstance(e))
                    (h as Handler<T>).handle(e)
        }
    }
    

    Here the Handler has an extra type field, which will need to be initialised by the constructor of every class implementing the type.  The type parameter needs a bound of Any (i.e. must be non-nullable, to match KClass).  And the as gives a warning as being an unchecked cast — but it should be safe, due to the isInstance() check (and the way that the type value is tied to the type parameter T).

    Another approach might be to store your handlers in a Map with the handler's type as the key, and a list of the corresponding handlers as the value, e.g.:

    interface Handler<T> : SomeHandler {
        fun handle(e: T)
    }
    
    class Sender(val handlers: Map<KClass<*>, List<SomeHandler>>) {
        fun <T> send(e: T) {
            handlers[T::class]?.forEach {
                (it as Handler<T>).handle(e)
            }
        }
    }
    

    The advantage of this approach is that it may be marginally more efficient, as it can go straight to the relevant handlers, without iterating through any others, and doesn't do any runtime type checks.  However, it's less safe: it relies on you setting up the handlers map so that each list holds only Handlers of the type given in its map key.  The type system can't enforce that for you: once again, the as is an unchecked cast, and you could get a runtime exception if the map wasn't set up right.

    I can't recommend either of these approaches: both are ugly, have some overhead, and the latter isn't very safe.  (There may be other approaches too, of course, but I suspect they'd have similar issues.)

    It's probably worth looking at your bigger picture, and seeing whether you need to do it this way at all; you may be able to make architectural changes which would allow a neater, safer approach.  (For example, you might look into parameterising Sender according to the types of Handler it uses.)