【问题标题】:Kotlin generics not being cast correctlyKotlin 泛型未正确转换
【发布时间】:2019-12-04 16:33:52
【问题描述】:

我有一个抽象类 (MessageHandlerAdapter),它接受一个泛型类型 (P),然后用于键入传递给它的函数之一的值:

abstract class MessageHandlerAdapter<in P : Any> {
    abstract fun canHandle(): MessageType
    abstract fun handle(payload: P)
}

如果我将它与List&lt;MessageHandlerAdapter&lt;*&gt;&gt; 一起使用,我会收到关于使用strategy.handle(message.payload) 的错误消息Out-projected type 'MessageHandlerAdapter&lt;*&gt;' prohibits the use of 'public abstract fun handle(payload: P): Unit defined in MessageHandlerAdapter'

class MessageHandlerStrategy(
    private val messageHandlers: List<MessageHandlerAdapter<*>>
) {
    private val strategies = messageHandlers.map { Pair(it.canHandle(), it) }.toMap()

    fun handle(message: Message<Any>) {
        val strategy = strategies[message.type]
            ?: throw IllegalArgumentException>(IllegalArgumentException("No MessageHandler for message of type ${message.type}"))

        strategy.handle(message.payload)
    }
}

但是,如果我将它与 List&lt;MessageHandlerAdapter&lt;Any&gt;&gt; 一起使用,我会在我尝试传入的列表中收到错误消息 Type inference failed. Expected type mismatch: required: List&lt;MessageHandlerAdapter&lt;Any&gt;&gt;found: List&lt;MessageHandlerAdapter&lt;*&gt;&gt;

lateinit var stringHandler: MessageHandlerAdapter<String>
lateinit var  intHandler: MessageHandlerAdapter<Int>
val messageHandlerStrategy = MessageHandlerStrategy(listOf(stringHandler, intHandler))

class MessageHandlerStrategy(
    private val messageHandlers: List<MessageHandlerAdapter<Any>>
) {
    private val strategies = messageHandlers.map { Pair(it.canHandle(), it) }.toMap()

    fun handle(message: Message<Any>) {
        val strategy = strategies[message.type]
            ?: throw IllegalArgumentException>(IllegalArgumentException("No MessageHandler for message of type ${message.type}"))

        strategy.handle(message.payload)
    }
}

我也尝试将MessageHandlerAdapter 的签名更改为abstract class MessageHandlerAdapter&lt;P : Any&gt; {...,但没有任何区别。

【问题讨论】:

    标签: generics kotlin casting


    【解决方案1】:

    由于您有不同类型的MessageHandlerAdapters,您的列表必须使用星形投影&lt;*&gt;,但这使得从您的列表中出来的对象对于调用任何将类型作为参数的函数都无用。您必须强制转换检索到的适配器才能在其上调用类型化函数。

    首先,我将消除 MessageType 类,只使用有效负载类型的 KClass 来简化我们处理它的方式,并消除创建 MessageType 和有效负载类型不匹配的消息的可能性:

    abstract class MessageHandlerAdapter<P : Any>(val payloadType: KClass<P>) {
        abstract fun handle(payload: P)
    }
    
    // Just an example, don't know what your Message class has
    data class Message<P : Any>(val payloadType: KClass<P>, val payload: P)
    

    那么 Message 的 payloadType 属性就可以方便地检查和转换了。

    在您的策略类中,您可以将您的 MessageHandlerAdapter&lt;*&gt; 转换为 MessageHandlerAdapter&lt;P&gt; 并知道这样做是安全的,因为您通过它的 payloadType 检索它,它必须与其通用类型相同。

    不幸的是,即使您使用try/catch,编译器也不够复杂,无法知道这一点,因此您会收到未经检查的强制转换警告。您可以使用@Suppress 隐藏警告。

    handle 函数还需要一个通用的 Message 类型,否则您只能传入 Message&lt;Any&gt;s。

    class MessageHandlerStrategy(
        private val messageHandlers: List<MessageHandlerAdapter<*>>
    ) {
    
        fun <P : Any> handle(message: Message<P>) {
            val strategy = messageHandlers.find { it.payloadType == message.payloadType }
                ?: throw IllegalArgumentException("No MessageHandler for message of type ${message.payloadType}")
    
            @Suppress("UNCHECKED_CAST")
            (strategy as MessageHandlerAdapter<Any>).handle(message.payload)
        }
    }
    

    【讨论】:

    • 感谢您的回答。消息是interface Message&lt;out P : Any&gt; { val payload: P, val type: MessageType }MessageType 只是一个枚举,用于定义消息是什么(更新 X、执行操作 Y 等)。归根结底,我从您的回复中得到的是,我们必须进行某种类型的选角。我很惊讶在 Java 中可以申请 MessageHandler&lt;Object&gt; 并且很高兴,但在 Kotlin 中,没有等价物。我明白为什么,我猜只是需要一些时间来适应。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多