【问题标题】:Kotlin out-projected type prohibits the useKotlin 外投影类型禁止使用
【发布时间】:2019-04-05 05:22:53
【问题描述】:

我最近在 Kotlin 中尝试了以下内容。 这个想法是我将收到一个扩展BaseItemItem(例如AmericanItem)作为输入。 我正在尝试为每个这些项目使用不同的解析器 这是一个示例代码

abstract class BaseItem
class AmericanItem : BaseItem()
class EuropeanItem : BaseItem()

interface ItemParser<T : BaseItem> {
    fun parse(item: T)
}

class AmericanItemParser : ItemParser<AmericanItem> {
    override fun parse(item: AmericanItem) {
        println("AmericanItemParser")
    }
}

class EuropeanItemParser : ItemParser<EuropeanItem> {
    override fun parse(item: EuropeanItem) {
        println("parsing EuropeanItem")
    }
}

fun main(args: Array<String>) {
    val hashMap = HashMap<Class<out BaseItem>, ItemParser<*>>()
    hashMap.put(AmericanItem::class.java, EuropeanItemParser())
    hashMap.put(EuropeanItem::class.java, AmericanItemParser())

    val inputItem = EuropeanItem()
    val foundParser = hashMap[inputItem.javaClass]
    foundParser?.parse(inputItem)
}

我的问题在最后一行,当我尝试调用解析器时,我收到以下编译错误

Out-projected type 'ItemParser<*>?' prohibits the use of 'public abstract fun parse(item: T): kotlin.Unit defined in ItemParser'

我在这里做错了什么?

【问题讨论】:

    标签: generics kotlin


    【解决方案1】:

    ItemParser&lt;*&gt; 是未知T 的解析器,而不是任何T。由于 T 是未知的,因此没有任何值可以安全地传递给期望 T 的函数 parse

    如果您确定映射包含给定 Class&lt;T&gt; 键的 ItemParser&lt;T&gt;,则可以编写以下使用未经检查的强制转换的辅助函数:

    fun <T : BaseItem> Map<Class<out T>, ItemParser<*>>.findParserFor(item: T) =
        get(item.javaClass) as ItemParser<T>?
    

    然后像这样使用它:

    val foundParser = hashMap.findParserFor(inputItem)
    foundParser?.parse(inputItem)
    

    请注意,在您的示例中,地图将 AmericanItem::class.java 键与 EuropeanItemParser 关联,反之亦然

    hashMap.put(AmericanItem::class.java, EuropeanItemParser())
    hashMap.put(EuropeanItem::class.java, AmericanItemParser())
    

    代码将因运行时异常而失败:

    ClassCastException:EuropeanItem 无法转换为 AmericanItem

    当您将EuropeanItem 传递给parse() 函数时会发生异常,而不是在findParserFor 函数中。发生这种情况是因为返回的ItemParser&lt;EuropeanItem&gt; 实例实际上是AmericanItemParser。这是使用未经检查的转换的结果,假设类型将匹配,而实际上它们不会匹配,这就是为什么该转换被称为“未经检查”的原因。

    【讨论】:

      【解决方案2】:

      您在MapItemParser 的声明之间产生了冲突。该映射可以包含BaseItem 的任何后代,但ItemParser 被设计为每个后代仅对BaseItem 的后代中的一个 进行操作。所以对于给定的ItemParser 实例,它必须接受它可以识别的东西,而在这里你不能这样做,因为你的foundParser 可以是任何后代,而不是给定ItemParser 实例的一个真正的预期类型。它应该猜哪个T?!?它不能。

      因此,您必须围绕基类而不是后代来设计 API。您使编译器无法知道传递给parse() 方法的内容。你能知道的唯一一件真实的事情是它是一个BaseItem 实例。

      只有知道您使用地图所做的技巧,以确保您使用正确的类型调用正确的实例。编译器不知道您的逻辑可以保证这一点。

      我建议您更改您的 API 以添加一个 internalParse 方法,您可以为该方法执行您的工作,并由一个通用的 parse 函数包装,该函数会仔细检查并执行邪恶的转换。

      abstract class BaseItem
      
      class AmericanItem : BaseItem()
      class EuropeanItem : BaseItem()
      
      interface ItemParser<T: BaseItem> {
          @Suppress("UNCHECKED_CAST")
          fun parse(item: BaseItem) {
              val tempItem = item as? T 
                   ?: throw IllegalArgumentException("Invalid type ${item.javaClass.name} passed to this parser")
              internalParse(tempItem)
          }
      
          fun internalParse(item: T)
      }
      
      class AmericanItemParser : ItemParser<AmericanItem> {
          override fun internalParse(item: AmericanItem) {
              println("AmericanItemParser")
          }
      }
      
      class EuropeanItemParser : ItemParser<EuropeanItem> {
          override fun internalParse(item: EuropeanItem) {
              println("parsing EuropeanItem")
          }
      }
      
      fun main(args: Array<String>) {
          val hashMap = HashMap<Class<out BaseItem>, ItemParser<*>>()
          hashMap.put(AmericanItem::class.java, EuropeanItemParser())
          hashMap.put(EuropeanItem::class.java, AmericanItemParser())
      
          val inputItem = EuropeanItem()
          val foundParser = hashMap[inputItem.javaClass]
          foundParser?.parse(inputItem)
      }
      

      请注意,您也可以使用 Kotlin 类而不是类型为 KClass&lt;out T&gt; 的 Java 类。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-09-29
        • 2018-06-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多