通用方差类型参数(Kotlin)

9rnv2umw  于 2023-11-21  发布在  Kotlin
关注(0)|答案(1)|浏览(118)

我不完全理解泛型中的variance是如何工作的。在下面的代码中,类如下: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)
}

字符串

xzlaal3s

xzlaal3s1#

我想你可能混淆了类声明站点泛型和使用站点泛型。

类声明站点泛型

在类声明处用协变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关键字使它更允许你传递什么类型的袋子给它,但它限制了你在函数中对每个包的操作。

相关问题