Map same generic type

Hey, I want the generic type in the map always to be the same. So KClass has the same as PacketListener, because just suppressing unchecked cast doesn’t seem to be the right way. Is it possible to define that the key and value should have the same generic?

object PacketManager {

    private val listeners =
        ConcurrentHashMap<KClass<out Packet>, PacketListener<out Packet>>()

    fun <T: Packet> registerListener(clazz: KClass<T>, packetListeners: PacketListener<T>) {
        listeners[clazz] = packetListeners
    }

    fun <T: Packet> process(packet: T, socketChannel: SocketChannel) {
        @Suppress("UNCHECKED_CAST")
        val packetListener = listeners[packet::class] as? PacketListener<T>
        packetListener?.accept(packet, socketChannel)
    }
}

Edit (Maybe you want to see PacketListener):

interface PacketListener<T : Packet> {
    fun accept(packet: T, socketChannel: SocketChannel)
}

No, it is not possible to specify a map like this. But this is a very typical case when we use unchecked casts.

Small improvements that I would personally make: T could be contravariant in PacketListener. PacketListener<out Packet> is just wrong, it is in, not out. I guess you made it out, so your registerListener compiles, but the proper solution is to use PacketListener<*>. The map doesn’t contain values of PacketListener<Packet> type, because that would mean every listener can consume any Packet and this is not true. Use *. Also, I don’t think as? can ever return null - just use as for simplicity.

The resulting code:

object PacketManager {

    private val listeners =
        ConcurrentHashMap<KClass<*>, PacketListener<*>>()

    fun <T: Packet> registerListener(clazz: KClass<T>, packetListeners: PacketListener<T>) {
        listeners[clazz] = packetListeners
    }

    fun <T: Packet> process(packet: T, socketChannel: SocketChannel) {
        @Suppress("UNCHECKED_CAST")
        val packetListener = listeners[packet::class] as PacketListener<T>
        packetListener.accept(packet, socketChannel)
    }
}

interface PacketListener<in T : Packet> {
    fun accept(packet: T, socketChannel: SocketChannel)
}
1 Like