通用方差类型参数(Kotlin)



我不完全理解泛型中的方差是如何工作的。在下面的代码中,类如下Any -> Mammals -> CatsAny是超类型,在copy function中有一个名为from的参数

根据我对outin关键字的理解,out允许引用它的任何subtype,只能生成而不能消费。

in允许引用它的任何一个supertype,只能消费而不能生产。

然而,在copytest function中,我们正在实例化函数copy。我在from参数中给了它一个catlist1参数。既然参数有一个out关键字,这不是意味着我们只能输入catlist2subtype参数吗?

更让我困惑的是,我看到了许多相互冲突的定义,例如,在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
}
}

这在逻辑上是有意义的,因为类泛型类型在声明位置没有差异限制。该功能有一个袋子,可以取出物品,还有一个袋子可以放入物品。这些inout关键字使它更允许您传递给它什么类型的袋子,但它限制了您在函数内对每个袋子所做的操作。

最新更新