Kotlin 将子 class 类型转换为父 class 类型

Kotlin put children class type into parent class type

我正在尝试创建一个 object 来收集 Message class 的子 class 的听众。

typealias Listener <T> = (T) -> Unit

object MessageRegistry {
    private val listeners = mutableMapOf<KClass<out Message>, MutableSet<Listener<Message>>>()

    fun <T : Message> listen(clazz: KClass<T>, listener: Listener<T>) {
        listeners.getOrPut(clazz) { mutableSetOf() }.add(listener)
    }
}

ConnectedMessageMessage 的子类型时,我希望此对象的客户端像下面这样注册他们的侦听器:

MessageRegistry.listen(ConnectedMessage::class) { connectedMessage: ConnectedMessage ->
  doSomething(connectedMessage)
}

不过,MessageRegistry.listen()方法好像有问题。 我无法将 listener 添加到 MutableSet<Listener<Message>>>

listener 有一个类型 (T) -> Unit,泛型 T<T: Message>
我想添加到类型为 (Message) -> UnitMutableSet

我收到如下类型错误:

Type mismatch.
Required:
Listener<Message> /* = (Message) → Unit */
Found:
Listener<T> /* = (T) → Unit */

正如我提到的 T 继承了 Message class (<T : Message>),我认为这是一个有效的转换。


我也试过将 out 标记为 Listener 的通用类型:

private val listeners = mutableMapOf<KClass<out Message>, MutableSet<Listener<out Message>>>()

但它给出了以下错误:

Conflicting projection in type alias expansion in intermediate type '(T) -> Unit'

我该怎么做才能让听众在一个集合中?

提前致谢。

你的 Listener 是一个 T 消费者,而不是生产者,所以它不能被赋予 out Message 类型并且仍然有用。

由于您的函数允许 Listener 的 Message 子类型,但 Listener 是消费者,因此您的 Listener 无法满足成为 Listener<Message> 的要求。例如,如果您有一个 Listener<SubMessage> 并试图将一个 Message 传递给它,这将是不安全的,因为 Message 可能不是 SubMessage.

但是,由于您将这些存储在具有 class 的 Map 中,您实际上是在创建 runtime-safety 类型,并且您可以从逻辑上推断类型如果匹配则可以安全转换.

因此,泛型系统无法保证安全,但如果您注意投射位置,则可以。你可以这样实现它:

object MessageRegistry {
    private val listeners = mutableMapOf<KClass<out Message>, MutableSet<Listener<*>>>()

    fun <T : Message> listen(clazz: KClass<T>, listener: Listener<T>) {
        listeners.getOrPut(clazz) { mutableSetOf() }.add(listener)
    }

    private fun <T: Message> getListeners(clazz: KClass<T>): MutableSet<Listener<T>> {
        @Suppress("UNCHECKED_CAST")
        return listeners[clazz] as MutableSet<Listener<T>>
    }
}

我建议制作这些 inline/reified 以使调用站点的代码更简单。在大多数情况下,它将能够在调用站点推断类型,因此您不必显式传递它。

object MessageRegistry {
    private val listeners = mutableMapOf<KClass<out Message>, MutableSet<Listener<*>>>()

    @PublishedApi
    internal fun <T : Message> listen(clazz: KClass<T>, listener: Listener<T>) {
        listeners.getOrPut(clazz) { mutableSetOf() }.add(listener)
    }
    
    inline fun <reified T : Message> listen(noinline listener: Listener<T>) {
        listen(T::class, listener)
    }

    private fun <T: Message> getListeners(clazz: KClass<T>): MutableSet<Listener<T>> {
        @Suppress("UNCHECKED_CAST")
        return listeners[clazz] as MutableSet<Listener<T>>
    }
}