通用方差类型参数(Kotlin)

问题描述 投票:0回答:1

我不完全理解泛型中的方差是如何工作的。在下面的代码中,类如下

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)
}
kotlin generics covariance contravariance
1个回答
1
投票

我认为您可能混淆了类声明站点泛型和使用站点泛型。


类声明站点泛型

在类声明处使用协变

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
关键字使您可以更允许传递给它的包类型,但它限制了您可以在函数内对每个包执行的操作。

© www.soinside.com 2019 - 2024. All rights reserved.