class Case<out T> {
private val contents = mutableListOf<T>()
fun produce(): T = contents.last() // Producer: OK
fun consume(item: T) = contents.add(item) // Consumer: Error
}
使用out修饰符声明的Case生成T及其子类型:
fun useProducer(case: Case<Rifle>) {
// Produces Rifle and its subtypes
val rifle = case.produce()
}
class Case<in T> {
private val contents = mutableListOf<T>()
fun produce(): T = contents.last() // Producer: Error
fun consume(item: T) = contents.add(item) // Consumer: OK
}
使用in修饰符声明的Case使用T及其子类型:
fun useConsumer(case: Case<Rifle>) {
// Consumes Rifle and its subtypes
case.consume(SniperRifle())
}
class Case<T> {
private val contents = mutableListOf<T>()
fun produce(): T = contents.last() // Producer: OK
fun consume(item: T) = contents.add(item) // Consumer: OK
}
在没有in或out修饰符的情况下声明的Case生成并使用T及其子类型:
fun useProducerConsumer(case: Case<Rifle>) {
// Produces Rifle and its subtypes
case.produce()
// Consumes Rifle and its subtypes
case.consume(SniperRifle())
}
abstract class Animal {
abstract fun speak()
}
class Dog: Animal() {
fun fetch() {}
override fun speak() { println("woof") }
}
class Cat: Animal() {
fun scratch() {}
override fun speak() { println("meow") }
}
fun addCat(animals: MutableList<Animal>) {
animals.add(Cat()) // uh oh, what if this is a list of Dogs?
}
fun main() {
val dogs: MutableList<Dog> = mutableListOf(Dog(), Dog())
addCat(dogs) // we just added a Cat to a list of Dogs!
val d: Dog = dogs.last() // list of Dogs, so return type of .last() is Dog
// but this is actually a Cat
d.fetch() // a Cat can't fetch, so what should happen here?
}
val dogs: MutableList<Dog> = mutableListOf(Dog())
val anything: MutableList<Any> = dogs
// now I can add any type I want to the dogs list through the anything list
anything.add("hello world")
> abstract class Source<out T> {
> abstract fun nextT(): T }
>
> fun demo(strs: Source<String>) {
> val objects: Source<Any> = strs // This is OK, since T is an out-parameter
> // ... }
9条答案
按热度按时间qnakjoqk1#
Kotlin中的
List<out T>
相当于Java中的List<? extends T>
。Kotlin中的
List<in T>
相当于Java中的List<? super T>
例如,在Kotlin中,您可以执行以下操作
理由是你可以标记通用“出”,如果你返回它,但永远不会收到。如果你收到了,你可以把它标记为“in”,但永远不要回来。
i2loujxw2#
变量修饰符
out
和in
允许我们通过子类型化来减少泛型类型的限制,提高泛型类型的可重用性。让我们借助对比的例子来理解这一点。我们将使用案例作为各种武器的容器。假设我们有以下类型层次结构:
out
生成T
并保留子类型当你用
out
修饰符声明一个泛型类型时,它被称为 * 协变 *。协变量是T
的 * producer *,这意味着函数可以返回T
,但不能将T
作为参数:使用
out
修饰符声明的Case
生成T
及其子类型:使用
out
修饰符,子类型被 * 保留 *,因此当SniperRifle
是Rifle
的子类型时,Case<SniperRifle>
是Case<Rifle>
的子类型。因此,useProducer()
函数也可以用Case<SniperRifle>
调用:这是 * 限制较少 * 和 * 更可重用 *,而生产,但我们的类成为 * 只读 *。
in
消耗T
并 * 反转 * 子类型当你声明一个带有
in
修饰符的泛型类型时,它被称为contravariant
。逆变是T
的 * 消费者 *,这意味着函数可以将T
作为参数,但它们不能返回T
:使用
in
修饰符声明的Case
使用T
及其子类型:使用
in
修饰符,子类型被 * 反转 *,所以现在Case<Weapon>
是Case<Rifle>
的子类型,而Rifle
是Weapon
的子类型。因此,useConsumer()
函数也可以用Case<Weapon>
调用:这在消费时 * 限制更少 * 并且 * 更可重用 *,但是我们的类变成了 * 只写 *。
Invariant产生并消耗
T
,* 不允许 * 子类型当你声明一个没有任何变化修饰符的泛型类型时,它被称为 * invariant *。不变量是
T
的生产者和消费者,这意味着函数可以将T
作为参数,也可以返回T
:在没有
in
或out
修饰符的情况下声明的Case
生成并使用T
及其子类型:如果没有
in
或out
修饰符,子类型是 * 不允许的 *,所以现在Case<Weapon>
和Case<SniperRifle>
都不是Case<Rifle>
的子类型。因此,useProducerConsumer()
函数只能用Case<Rifle>
调用:这在生产和消费时 * 限制性更强 * 和 * 可重用性更低 *,但我们可以 * 读取和写入 *。
总结
Kotlin中的
List
只是一个生产者。因为它是使用out
修饰符声明的:List<out T>
.这意味着您不能向它添加元素,因为add(element: T)
是一个消费者函数。无论何时,如果你想同时处理get()
和add()
元素,请使用不变版本MutableList<T>
。就是这样!希望这有助于理解方差的
in
s和out
s!wztqucjr3#
使用此签名:
你可以这样做:
这意味着T是协变:
当类C的类型参数T被声明为out时,C可以安全地成为C的超类型。can safely be asupertypeofC .
这与中的**形成对比,例如
你可以这样做:
这意味着T是逆变:
当类C的类型参数T在中声明时,C可以安全地成为C的超类型**。can safely be asupertypeofC .
另一种方式来记住它:
消费者进**,生产者出。
参见Kotlin泛型方差
对于“Consumer in,Producer out”,我们只从Producer-call方法中读取T类型的结果;并且只通过传入类型T的参数来写入Consumer-call方法。
在
List<out T>
的例子中,很明显我们可以这样做:因此,当需要
List<Number>
时,提供List<Double>
是安全的,因此List<Number>
是List<Double>
的超类型,但反之亦然。在
Comparable<in T>
的示例中:因此,当需要
Comparable<Double>
时,提供Comparable<Number>
是安全的,因此Comparable<Double>
是Comparable<Number>
的超类型,但反之亦然。pwuypxnk4#
这些答案解释了**
out
的作用,但没有解释**为什么需要它,所以让我们假设我们根本没有out
。假设有三个类:Animal、Cat、Dog和一个接受Animal
列表的函数由于
Dog
是Animal
的子类型,我们希望使用List<Dog>
作为List<Animal>
的子类型,这意味着我们希望能够这样做:这很好,代码将为狗打印
woof woof
,为混合列表打印woof meow
。问题是当我们有一个可变容器时。由于
List<Animal>
可以包含Dog
和Cat
,因此我们可以将任意一个添加到MutableList<Animal>
你不能安全地认为
MutableList<Dog>
是MutableList<Animal>
的子类型,因为你可以对后者做一些你不能对前者做的事情(插入一只猫)。举一个更极端的例子:
仅在向列表中添加而不是从列表中阅读时出现问题。将
List<Dog>
用作List<Animal>
是安全的,因为您不能追加到List
。这就是out
告诉我们的。out
说“这是我输出的类型,但我不把它作为我消费的新输入”jk9hmnmh5#
记住这样:
in
是“forinput”-你想把东西放进去(写)进去(所以它是一个“消费者”)out
是“foroutput”-你想从中获取(阅读)一些东西(所以它是一个“生产者”)如果你来自 java
<in T>
用于输入,因此它类似于<? super T>
(消费者)<out T>
用于输出,因此类似于<? extends T>
(生产者)rmbxnbpk6#
produce = output = out.
如果泛型类只使用泛型类型作为它的函数的输出,那么使用out
consume = input = in.
如果泛型类只使用泛型类型作为它的函数的输入,那么使用in
详细说明请参考https://medium.com/mobile-app-development-publication/in-and-out-type-variant-of-kotlin-587e4fa2944c。
r8xiu3jd7#
参考Kotlin手册
Kotlin
List<out T>
类型是一个提供只读操作的接口,如size,get等。与Java一样,它继承自Collection<T>
,而Collection<T>
又继承自Iterable<T>
。更改列表的方法由MutableList<T>
接口添加。此模式也适用于Set<out T>/MutableSet<T>
和Map<K, out
V>/MutableMap<K, V>
还有这个
在Kotlin中,有一种方法可以向编译器解释这类事情。这被称为声明站点差异:我们可以注解Source的类型参数T,以确保它只从
Source<T>
的成员返回(产生),并且永远不会被消耗。为此,我们提供了out修饰符:一般规则是:当类
C
的类型参数T
被声明为out时,它可能只出现在C
的成员中的out位置,但反过来C<Base>
可以安全地成为C<Derived>
的超类型。在“聪明的话”中,他们说类
C
在参数T
中是协变的,或者T
是一个协变类型参数。你可以把C看作是T的生产者,而不是T
的消费者。out修饰符被称为方差注解,由于它是在类型参数声明位置提供的,因此我们讨论声明位置的方差。这与Java的使用位置变化形成对比,其中类型使用中的通配符使类型协变。5lhxktic8#
您可以将其视为:
就这么简单
2sbarzqh9#
Functional programming in Kotlin的一个很好的解释:
请考虑以下代码:
在声明类List中,类型参数A前面的out是一个方差注解,表示A是List的协变或“正”参数。例如,这意味着List被认为是List的子类型,假设Dog是Animal的子类型。(更一般地,对于所有类型X和Y,如果X是Y的子类型,则List是List的子类型。)我们可以省略A前面的out,这将使List在该类型参数中保持不变。但请注意,现在Nil扩展了List。Nothing是所有类型的子类型,这意味着结合variance注解,Nil可以被认为是一个List,一个List,等等,正如我们所希望的那样。