为什么在泛型中禁止使用 'out' 关键字,如果一个方法除了类型参数作为参数?
Why it is forbidden to use 'out' keyword in generics if a method excepts the type parameter as a parameter?
我正在寻找一个示例,当在 class 声明中使用 out 并且 class 有一个获取参数类型作为参数。
此外,我正在寻找一个在 class 声明中使用 in 并且参数类型为 var 时可能导致问题的示例 class 成员?
我认为我将能够仅通过示例来理解规则
关于方差的小提示
当你有一个泛型 class G<T>
(参数化类型)时,差异是关于为不同的 T
定义类型 G<T>
的层次结构之间的关系s,以及不同 T
s 本身的层次结构。
例如,如果 child class C
扩展 parent P
那么:
List<C>
是否扩展 List<P>
? (List<T>
在 T
中是协变的)
- 还是反过来? (逆变)
- 或者
List<C>
和List<P>
没有关系? (不变)。
例子
现在,考虑 List<out T>
,这意味着 List
在 T
中是 协变的 。
正如我们刚刚看到的,这样声明列表意味着以下内容成立:“如果 C
扩展 P
,则 List<C>
扩展 List<P>
”。
让我们假设以下 class 声明:
open class Parent {
fun doParentStuff()
}
class Child : Parent() {
fun doChildStuff()
}
List<out T>
的协方差意味着这是可能的:
val listOfChild: List<Child> = listOf<Child>(Child(), Child())
// this is ok because List is covariant in T (out T)
// so List<Child> is a subtype of List<Parent>, and can be assigned to listOfParent
val listOfParent: List<Parent> = listOfChild
那么如果我们可以在 List
class 中声明一个接受参数 T
的方法会发生什么?
class List<out T> {
fun add(element: T) {
// I can guarantee here that I have an instance of T, right?
}
}
大多数语言(包括 Kotlin)的规则规定,如果一个方法接受类型为 T
的参数,从技术上讲,您可以获得 T
或任何子类的实例class of T(这就是 subclassing 的意义所在),但你至少拥有所有可用的 T API。
但是请记住我们声明了List<out T>
,这意味着我可以这样做:
val listOfChild: List<Child> = listOf<Child>(Child(), Child())
// this is ok because List is covariant in T (out T)
val listOfParent: List<Parent> = listOfChild
// listOfChild and listOfParent point to the same list instance
// so here we are effectively adding a Parent instance to the listOfChild
listOfParent.add(Parent())
// oops, the last one is not an instance of Child, bad things will happen here
// we could fail right here at runtime because Parent cannot be cast to Child
val child: Child = listOfChild.last
// even worse, look at what looks possible, but is not:
child.doChildThing()
在这里你可以看到,从 List<Child>
实例中,我们实际上可以收到一个 Parent
的实例,它不是 Child
的子 class声明了 Child
.
类型参数的方法
假设这些是我们正在使用的 classes:
open class Animal
class Cat: Animal() {
fun meow() = println("meow")
}
如果我们用协变 out
类型创建这样的 class 并且编译器允许我们将该类型用作函数参数:
class Foo<out T: Animal> {
private var animal: T? = null
fun consumeValue(x: T) { // NOT ALLOWED
animal = x
}
fun produceValue(): T? {
return animal
}
}
那么如果你这样做,将导致我们试图在没有 meow
函数的 Animal 上调用 meow
的不可能情况:
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
并且如果我们使用逆变 in
类型创建这样的 class 并且编译器允许我们将该类型用作 return 值:
class Bar<in T: Animal>(private val library: List<T>) {
fun produceValue(): T { // NOT ALLOWED
return library.random()
}
}
那么如果你这样做,将导致编译器不可能将 return 类型转换为子类型。
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
一个 属性 有一个 getter 就像一个函数 return 是一个值, var
属性 另外还有一个 setter,这就像一个带参数的函数。因此 val
属性与逆变(in
)不兼容,并且 var
属性与逆变或协变不兼容(out
)。私有属性不受这些限制的影响,因为在 class 的内部工作中,类型是不变的。所有 class 可以知道的关于它自己的类型的就是它的边界。方差只会影响 class 被外界投射(查看)的方式。
所以一个 val
的例子足以说明为什么 属性 与逆变不兼容。您可以将下面的 val
替换为 var
,这不会有什么不同。
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
我正在寻找一个示例,当在 class 声明中使用 out 并且 class 有一个获取参数类型作为参数。
此外,我正在寻找一个在 class 声明中使用 in 并且参数类型为 var 时可能导致问题的示例 class 成员? 我认为我将能够仅通过示例来理解规则
关于方差的小提示
当你有一个泛型 class G<T>
(参数化类型)时,差异是关于为不同的 T
定义类型 G<T>
的层次结构之间的关系s,以及不同 T
s 本身的层次结构。
例如,如果 child class C
扩展 parent P
那么:
List<C>
是否扩展List<P>
? (List<T>
在T
中是协变的)- 还是反过来? (逆变)
- 或者
List<C>
和List<P>
没有关系? (不变)。
例子
现在,考虑 List<out T>
,这意味着 List
在 T
中是 协变的 。
正如我们刚刚看到的,这样声明列表意味着以下内容成立:“如果 C
扩展 P
,则 List<C>
扩展 List<P>
”。
让我们假设以下 class 声明:
open class Parent {
fun doParentStuff()
}
class Child : Parent() {
fun doChildStuff()
}
List<out T>
的协方差意味着这是可能的:
val listOfChild: List<Child> = listOf<Child>(Child(), Child())
// this is ok because List is covariant in T (out T)
// so List<Child> is a subtype of List<Parent>, and can be assigned to listOfParent
val listOfParent: List<Parent> = listOfChild
那么如果我们可以在 List
class 中声明一个接受参数 T
的方法会发生什么?
class List<out T> {
fun add(element: T) {
// I can guarantee here that I have an instance of T, right?
}
}
大多数语言(包括 Kotlin)的规则规定,如果一个方法接受类型为 T
的参数,从技术上讲,您可以获得 T
或任何子类的实例class of T(这就是 subclassing 的意义所在),但你至少拥有所有可用的 T API。
但是请记住我们声明了List<out T>
,这意味着我可以这样做:
val listOfChild: List<Child> = listOf<Child>(Child(), Child())
// this is ok because List is covariant in T (out T)
val listOfParent: List<Parent> = listOfChild
// listOfChild and listOfParent point to the same list instance
// so here we are effectively adding a Parent instance to the listOfChild
listOfParent.add(Parent())
// oops, the last one is not an instance of Child, bad things will happen here
// we could fail right here at runtime because Parent cannot be cast to Child
val child: Child = listOfChild.last
// even worse, look at what looks possible, but is not:
child.doChildThing()
在这里你可以看到,从 List<Child>
实例中,我们实际上可以收到一个 Parent
的实例,它不是 Child
的子 class声明了 Child
.
假设这些是我们正在使用的 classes:
open class Animal
class Cat: Animal() {
fun meow() = println("meow")
}
如果我们用协变 out
类型创建这样的 class 并且编译器允许我们将该类型用作函数参数:
class Foo<out T: Animal> {
private var animal: T? = null
fun consumeValue(x: T) { // NOT ALLOWED
animal = x
}
fun produceValue(): T? {
return animal
}
}
那么如果你这样做,将导致我们试图在没有 meow
函数的 Animal 上调用 meow
的不可能情况:
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
并且如果我们使用逆变 in
类型创建这样的 class 并且编译器允许我们将该类型用作 return 值:
class Bar<in T: Animal>(private val library: List<T>) {
fun produceValue(): T { // NOT ALLOWED
return library.random()
}
}
那么如果你这样做,将导致编译器不可能将 return 类型转换为子类型。
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
一个 属性 有一个 getter 就像一个函数 return 是一个值, var
属性 另外还有一个 setter,这就像一个带参数的函数。因此 val
属性与逆变(in
)不兼容,并且 var
属性与逆变或协变不兼容(out
)。私有属性不受这些限制的影响,因为在 class 的内部工作中,类型是不变的。所有 class 可以知道的关于它自己的类型的就是它的边界。方差只会影响 class 被外界投射(查看)的方式。
所以一个 val
的例子足以说明为什么 属性 与逆变不兼容。您可以将下面的 val
替换为 var
,这不会有什么不同。
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