简体   繁体   English

通用方差类型参数(Kotlin)

[英]Generic variance type parameter(Kotlin)

I do not fully understand how variance in Generics work.我不完全理解 Generics 的变化是如何工作的。 In the code below the classes are as follows Any -> Mammals -> Cats .在下面的代码中,类如下Any -> Mammals -> Cats Any is the supertype, there is a parameter called from in the copy function Any是超类型,在copy function中有一个参数调用from

From what I understand about the out and in keywords, out allows reference to any of it's subtype , can only be produced not consumed.根据我对outin关键字的了解, out允许引用它的任何subtype ,只能产生不能使用。

in allows reference to any of it's supertype , can only be consumed not produced. in允许引用它的任何supertype ,只能消费不能生产。

However in the copytest function we are instantiating the function copy .但是在copytest function中,我们正在实例化 function copy I gave it a catlist1 argument in the from parameter.我在from参数中给了它一个catlist1参数。 Since the parameter has an out keyword wouldn't it mean that we can only input parameters that are a subtype of catlist2 ?由于参数有一个out关键字,这是否意味着我们只能输入属于catlist2 subtype的参数?

To top of my confusion I have seen many conflicting definitions, for instance, In Kotlin, we can use the out keyword on the generic type which means we can assign this reference to any of its supertypes.最让我困惑的是,我看到了许多相互冲突的定义,例如,在 Kotlin 中,我们可以在泛型类型上使用out关键字,这意味着我们可以将此引用分配给它的任何超类型。

Now I am really confused could anybody guide me on how all of these works?现在我真的很困惑,有人可以指导我了解所有这些工作原理吗? Preferably from scratch, thanks!最好从头开始,谢谢!

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)
}

I think maybe you're mixing up class-declaration-site generics and use-site generics.我想你可能混淆了类声明站点 generics 和使用站点 generics。


Class-declaration-site generics类声明站点 generics

Defined at the class declaration site with covariant out , it is true you cannot use the generic type as the type of a function parameter for any functions in the class.在 class 声明站点用协变out定义,确实不能将泛型类型用作 function 参数的类型,用于 class 中的任何函数。

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!

It is not logical to say, "I'm going to put a horse in a list that may have the requirement that all items retrieved from it must be cows."说“我将把一匹马放在一个可能要求从中检索到的所有项目必须是奶牛的列表中”是不合逻辑的。


Use-site generics使用现场 generics

This has nothing to do with the class level restriction.这与 class 级别限制无关。 It's only describing what kind of input the function gets.它只是描述 function 得到什么样的输入。 It is perfectly logical to say, "my function does something with a container that I'm going to pull something out of".完全合乎逻辑的说法是,“我的 function 对我要从中拉出一些东西的容器做了一些事情”。

// 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.

Combining both结合两者

What could be confusing to you is if you saw an example that combines both of the above.如果您看到一个结合了上述两者的示例,您可能会感到困惑。 You can have a class where T is a declaration site type, but the class has functions where there are input parameters where T is part of the definition of what parameters the function can take.您可以有一个 class ,其中T是声明站点类型,但 class 具有输入参数的功能,其中T是 function 可以采用的参数定义的一部分。 For example:例如:

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
    }
}

This logically makes sense since the class generic type has no variance restriction at the declaration site.这在逻辑上是有道理的,因为 class 泛型类型在声明站点没有变化限制。 This function has one bag that it's allowed to take items out of, and one bag that it's allowed to put items into.这个function有一个可以取出物品的袋子和一个可以放入物品的袋子。 These in and out keywords make it more permissive of what types of bags you can pass to it, but it limits what you're allowed to do with each of those bags inside the function.这些inout关键字使其更允许您传递给它的包类型,但它限制了您可以对 function 中的每个包执行的操作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM