我需要Kotlin中的Collection只包含实现给定接口的元素。
例如:包含动物集合的地图:
interface Animal { val name: String }
data class Monkey(override val name: String): Animal
data class Snake(override val name: String): Animal
通过阅读文档和博客以及SO问题,我编写了使用Generics 关键字的代码:
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&lt; out Animal&gt;:
...
(animals as ArrayList<out Animal>).forEach { animal: Animal ->
println("\t$animal")
}
...
但我不确定这是编写此类代码的最佳方式。 有没有更好的方法告诉Kotlin我想在生产者和消费者的泛型中使用子类型?
答案 0 :(得分:4)
我认为您不需要in
类型的data
关键字。
在这里使用in
意味着您希望这些ArrayList
的类型参数至少与Animal
一样,这意味着ArrayList<in Animal>
1}}实际上也可以使用超类型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")
}
}
}