Kotlin 为什么禁止使用';输出';如果方法将类型参数作为参数之外的参数,是否在泛型中使用关键字?

Kotlin 为什么禁止使用';输出';如果方法将类型参数作为参数之外的参数,是否在泛型中使用关键字?,kotlin,generics,covariance,contravariance,Kotlin,Generics,Covariance,Contravariance,我正在寻找一个示例,当在类内声明中使用out时可能会导致问题,并且该类有一个将参数类型作为参数的方法 另外,我正在寻找一个在类声明中使用时可能会导致问题的示例,并且参数类型是类的var成员? 我想我只能通过例子来理解规则关于差异的小提示 当您有一个泛型类G(参数化类型)时,差异是关于定义不同Ts的类型G的层次结构与不同Ts本身的层次结构之间的关系 例如,如果子类C扩展父类p,则: List是否扩展List?(List在T中是协变的) 还是相反?(逆变) 或者List和List之间没有关系?(不

我正在寻找一个示例,当在类内声明中使用out时可能会导致问题,并且该类有一个将参数类型作为参数的方法

另外,我正在寻找一个在类声明中使用时可能会导致问题的示例,并且参数类型是类的var成员? 我想我只能通过例子来理解规则

关于差异的小提示 当您有一个泛型类
G
(参数化类型)时,差异是关于定义不同
T
s的类型
G
的层次结构与不同
T
s本身的层次结构之间的关系

例如,如果子类
C
扩展父类
p
,则:

  • List
    是否扩展
    List

    ?(
    List
    T
    中是协变的)
  • 还是相反?(逆变)
  • 或者
    List
    List

    之间没有关系?(不变量)
实例

现在,考虑<代码>清单>代码>,这意味着<代码>列表 >在代码> t>代码>中是协变的。 正如我们刚才所看到的,将列表声明为这样意味着以下内容:“如果

C
扩展
P
,那么
list
扩展
list

让我们在这里假设以下类声明:

开放类父类{
fun doParentStuff()
}
类子级:父级(){
乐趣多奇尔德斯图夫()
}
列表的协方差意味着这是可能的:

val listOfChild:List=listOf(Child(),Child())
//这是可以的,因为列表在T(out T)中是协变的
//所以List是List的一个子类型,可以分配给listOfParent
val listOfParent:List=listOfChild
那么,如果我们可以在
列表
类中声明一个接受参数
T
的方法,会发生什么呢

类列表{
趣味添加(元素:T){
//我可以保证这里有一个T的实例,对吗?
}
}
大多数语言(包括Kotlin)的规则都规定,如果一个方法接受类型为
T
的参数,从技术上讲,您可以获得
T
的一个实例或T的任何子类(这是子类化的要点),但您至少可以获得T的所有API

但请记住,我们声明了
List
,这意味着我可以:

val listOfChild:List=listOf(Child(),Child())
//这是可以的,因为列表在T(out T)中是协变的
val listOfParent:List=listOfChild
//listOfChild和listOfParent指向同一个列表实例
//因此,这里我们有效地向listOfChild添加了一个父实例
添加(父项())
//哎呀,最后一个不是Child的实例,这里会发生不好的事情
//我们可能会在运行时失败,因为无法将父对象强制转换为子对象
val child:child=listOfChild.last
//更糟糕的是,看看什么是可能的,但不是:
child.doChildThing()
在这里您可以看到,从
列表
实例中,我们实际上可以接收到一个
Parent
实例,该实例不是
Child
的子类,该方法声明了一个类型为
Child

的参数,这是一个关于差异的小提示 当您有一个泛型类
G
(参数化类型)时,差异是关于定义不同
T
s的类型
G
的层次结构与不同
T
s本身的层次结构之间的关系

例如,如果子类
C
扩展父类
p
,则:

  • List
    是否扩展
    List

    ?(
    List
    T
    中是协变的)
  • 还是相反?(逆变)
  • 或者
    List
    List

    之间没有关系?(不变量)
实例

现在,考虑<代码>清单>代码>,这意味着<代码>列表 >在代码> t>代码>中是协变的。 正如我们刚才所看到的,将列表声明为这样意味着以下内容:“如果

C
扩展
P
,那么
list
扩展
list

让我们在这里假设以下类声明:

开放类父类{
fun doParentStuff()
}
类子级:父级(){
乐趣多奇尔德斯图夫()
}
列表的协方差意味着这是可能的:

val listOfChild:List=listOf(Child(),Child())
//这是可以的,因为列表在T(out T)中是协变的
//所以List是List的一个子类型,可以分配给listOfParent
val listOfParent:List=listOfChild
那么,如果我们可以在
列表
类中声明一个接受参数
T
的方法,会发生什么呢

类列表{
趣味添加(元素:T){
//我可以保证这里有一个T的实例,对吗?
}
}
大多数语言(包括Kotlin)的规则都规定,如果一个方法接受类型为
T
的参数,从技术上讲,您可以获得
T
的一个实例或T的任何子类(这是子类化的要点),但您至少可以获得T的所有API

但请记住,我们声明了
List
,这意味着我可以:

val listOfChild:List=listOf(Child(),Child())
//这是可以的,因为列表在T(out T)中是协变的
val listOfParent:List=listOfChild
//listOfChild和listOfParent指向同一个列表实例
//因此,这里我们有效地向listOfChild添加了一个父实例
添加(父项())
//哎呀,最后一个不是Child的实例,这里会发生不好的事情
//我们可能会在运行时失败,因为无法将父对象强制转换为子对象
val child:child=listOfChild.last
//甚至
open class Animal

class Cat: Animal() {
    fun meow() = println("meow")
}
class Foo<out T: Animal> {
    private var animal: T? = null

    fun consumeValue(x: T) { // NOT ALLOWED
        animal = x
    }

    fun produceValue(): T? {
        return animal
    }
}
val catConsumer = Foo<Cat>()
val animalConsumer: Foo<Animal> = catConsumer // upcasting is valid for covariant type
animalConsumer.consumeValue(Animal())
catConsumer.produceValue()?.meow() // can't call `meow` on plain Animal
class Bar<in T: Animal>(private val library: List<T>) {
    fun produceValue(): T  { // NOT ALLOWED
        return library.random()
    }
}
val animalProducer: Bar<Animal> = Bar(List(5) { Animal() })
val catProducer: Bar<Cat> = animalProducer // downcasting is valid for contravariant type
catProducer.produceValue().meow() // can't call `meow` on plain Animal
class Bar<in T: Animal>(
    val animal: T // NOT ALLOWED
)

val animalProducer: Bar<Animal> = Bar(Animal())
val catProducer: Bar<Cat> = animalProducer // downcasting is valid for contravariant type
catProducer.animal.meow() // can't call `meow` on plain Animal