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.
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 Handler
s 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.)