我不完全理解泛型中的方差是如何工作的。在下面的代码中,类如下
Any -> Mammals -> Cats
。 Any
是超类型,from
中有一个参数叫
copy function
根据我对
out
和 in
关键字的理解,out
允许引用其中的任何一个 subtype
,只能生产而不能消费。
in
允许引用任何 supertype
,只能消费不能生产。
但是在
copytest function
中,我们正在实例化函数 copy
。我在 catlist1
参数中给了它一个 from
参数。既然参数有一个 out
关键字,是不是意味着我们只能输入 subtype
的 catlist2
的参数?
最让我困惑的是,我看到了许多相互冲突的定义,例如,在 Kotlin 中,我们可以在泛型类型上使用
out
关键字,这意味着我们可以将此引用分配给它的任何超类型。
现在我真的很困惑,有人可以指导我所有这些是如何工作的吗?最好从头开始,谢谢!
class List2<ITEM> {
val data = mutableListOf<ITEM>()
fun get(n: Int): ITEM = data[n]
fun add(item: ITEM) { data.add(item) }
}
fun <T> copy(from: List2<out T>, to: List2<T>) {
}
fun copytest() {
val catList1 = List2<Cat>()
val catList2 = List2<Cat>()
val mammalList = List2<Mammal>()
copy(catList1, mammalList)
}
我认为您可能混淆了类声明站点泛型和使用站点泛型。
类声明站点泛型
在类声明处使用协变
out
定义,确实不能使用泛型类型作为类中任何函数的函数参数类型。
class MyList<out T>(
private val items: Array<T>
) {
fun pullRandomItem(): T { // allowed
return items.random()
}
fun addItem(item: T) { // Not allowed by compiler!
// ...
}
}
// Reason:
val cowList = MyList<Cow>(arrayOf(Cow()))
// The declaration site out covariance allows us to up-cast to a more general type.
// It makes logical sense, any cow you pull out of the original list qualifies as an animal.
val animalList: MyList<Animal> = cowList
// If it let us put an item in, though:
animalList.addItem(Horse())
// Now there's a horse in the cow list. That doesn't make logical sense
cowList.pullRandomItem() // Might return a Horse, impossible!
这样说是不合逻辑的:“我要把一匹马放入一个列表中,该列表可能要求从它检索到的所有项目都必须是牛。”
使用站点泛型
这与班级等级限制无关。它只是描述函数获得什么样的输入。说“我的函数对一个容器做了一些事情,我将从中取出一些东西”是完全合乎逻辑的。
// Given a class with no declaration-site covariance of contravariance:
class Bag<T: Any>(var contents: T?)
// This function will take any bag of food as a parameter. Inside the function, it will
// only get things out of the bag. It won't put things in it. This makes it possible
// to pass a Bag of Chips or a Bag of Pretzels
fun eatBagContents(bagOfAnything: Bag<out Food>) {
eat(bagOfAnything.contents) // we know the contents are food so this is OK
bagOfAnything.contents = myChips // Not allowed! we don't know what kind of stuff
// this bag is permitted to contain
}
// If we didn't define the function with "out"
fun eatBagContentsAndPutInSomething(bagOfAnything: Bag<Food>) {
eat(bagOfAnything.contents) // this is fine, we know it's food
bagOfAnything.contents = myChips // this is fine, the bag can hold any kind of Food
}
// but now you cannot do this
val myBagOfPretzels: Bag<Pretzels> = Bag(somePretzels)
eatBagContentsAndPutInSomething(myBagOfPretzels) // Not allowed! This function would
// try to put chips in this pretzels-only bag.
两者结合
如果您看到一个结合了上述两者的示例,可能会让您感到困惑。您可以拥有一个类,其中
T
是声明站点类型,但该类具有带有输入参数的函数,其中 T
是函数可以采用的参数定义的一部分。例如:
abstract class ComplicatedCopier<T> {
abstract fun createCopy(item: T): T
fun createCopiesFromBagToAnother(copyFrom: Bag<out T>, copyTo: Bag<in T>) {
val originalItem = copyFrom.contents
val copiedItem = createCopy(originalItem)
copyTo.contents = copiedItem
}
}
这在逻辑上是有意义的,因为类泛型类型在声明站点没有变体限制。该功能有一个允许从其中取出物品的袋子,以及一个允许其放入物品的袋子。这些
in
和 out
关键字使您可以更允许传递给它的包类型,但它限制了您可以在函数内对每个包执行的操作。