Kotlin泛型:无法推断类型参数

5

我需要一个 Kotlin 集合,其中只包含实现了特定接口的元素。

例如:一个包含动物集合的 Map:

interface Animal { val name: String }
data class Monkey(override val name: String): Animal
data class Snake(override val name: String): Animal

阅读文档、博客和SO问题后,我编写了使用 Generics 中的 in 关键字的代码:
class Test {
    private val data = HashMap<String, ArrayList<in Animal>>()        
    init {
        data.put("Monkeys", arrayListOf(Monkey("Kong"), Monkey("Cheetah")))
        data.put("Snakes", arrayListOf(Snake("Monthy"), Snake("Kaa")))
    }        
}

现在我想在Test类中添加一个方法,读取"data"的内容,例如将其打印到控制台:

fun printAll() {
   data.forEach { collectionName: String, animals: ArrayList<in Animal> -> 
       println(collectionName)
       animals.forEach { animal: Animal ->
           println("\t$animal")
       }
    }
}

如果我这样做,就会出现编译错误:

Error:(27, 21) Kotlin: Type inference failed: Cannot infer type parameter T in inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit
None of the following substitutions
receiver: Iterable<Any?>  arguments: ((Any?) -> Unit)
receiver: Iterable<Animal>  arguments: ((Animal) -> Unit)
can be applied to
receiver: kotlin.collections.ArrayList<in Animal> /* = java.util.ArrayList<in Animal> */  arguments: ((Animal) -> Unit)

我的解决方案是将我的动物强制转换为一个 ArrayList<输出 Animal>:
...
(animals as ArrayList<out Animal>).forEach { animal: Animal ->
    println("\t$animal")
}
...

但我不确定这是否是编写此类代码的最佳方式。有没有更好的方法告诉 Kotlin 我想在泛型中使用子类型作为生成者和消费者?

1个回答

4

我认为在data类型中不需要使用in关键字。

在此处使用in意味着您希望这些ArrayList的类型参数至少与Animal一样普遍,这意味着一个ArrayList<in Animal>实际上也可以用超类的参数化类型来参数化:您甚至可以将ArrayList<Any>放入映射中,这表明期望列表仅包含Animal是不安全的。

考虑删除in关键字,只保留ArrayList<Animal>(甚至是只读列表的接口List<Animal>):

private val data = HashMap<String, List<Animal>>()

init {
    data.put("Monkeys", listOf(Monkey("Kong"), Monkey("Cheetah")))
    data.put("Snakes", listOf(Snake("Monthy"), Snake("Kaa")))
}

fun printAll() {
    data.forEach { collectionName: String, animals: List<Animal> ->
        println(collectionName)
        animals.forEach { animal: Animal ->
            println("\t$animal")
        }
    }
}

1
实际上,切换到不可变的List有一个额外的好处:现在可以将listOf(Monkey(“Kong”),Monkey(“Cheetah”))视为List <Monkey>而不是List <Animal> - Oliver Charlesworth
1
这个解决方案也适用于MutableList接口,这非常方便,如果我需要一个检索列表以便修改它的函数。 - Guillaume
1
在类签名中,更常见的是看到in/out通用关键字来定义类是否“消耗”或“生成”该类型。您可以在此处阅读有关这些关键字的信息:https://kotlinlang.org/docs/reference/generics.html#declaration-site-variance - M. Palsich

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接