我无法理解,而且我无法在kotlin中找到 out 关键字的含义。
您可以在此处查看示例:
List<out T>
如果有人能解释这个的含义。真的很感激。
答案 0 :(得分:38)
差异修饰符 out
和 in
允许我们通过允许子类型化来减少泛型类型的限制并提高可重用性。
让我们借助对比示例来理解这一点。我们将使用案例作为各种武器的容器。假设我们有以下类型层次结构:
open class Weapon
open class Rifle : Weapon()
class SniperRifle : Rifle()
out
生成 T
并保留子类型当您使用 out
修饰符声明泛型类型时,它被称为 covariant。协变是 T
的生产者,这意味着函数可以返回 T
但它们不能将 T
作为参数:
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
}
使用 Case
修饰符声明的 out
生成 T
及其子类型:
fun useProducer(case: Case<Rifle>) {
// Produces Rifle and its subtypes
val rifle = case.produce()
}
使用 out
修饰符时,子类型被保留,因此当 Case<SniperRifle>
是 Case<Rifle>
的子类型时,SniperRifle
是 Rifle
的子类型useProducer()
。因此,Case<SniperRifle>
函数也可以用 useProducer(Case<SniperRifle>()) // OK
useProducer(Case<Rifle>) // OK
useProducer(Case<Weapon>()) // Error
调用:
in
这在生成时限制更少并且更可重用,但我们的类变为只读。
T
消耗 in
并反转子类型当您使用 contravariant
修饰符声明泛型类型时,它称为 T
。逆变器是 T
的 消费者,这意味着函数可以将 T
作为参数,但它们不能返回 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
}
:
Case
使用 in
修饰符声明的 T
使用 fun useConsumer(case: Case<Rifle>) {
// Consumes Rifle and its subtypes
case.consume(SniperRifle())
}
及其子类型:
in
使用 Case<Weapon>
修饰符,子类型被反转,所以现在当 Case<Rifle>
是子类型时,Rifle
是 Weapon
的子类型useConsumer()
。因此,Case<Weapon>
函数也可以用 useConsumer(Case<SniperRifle>()) // Error
useConsumer(Case<Rifle>()) // OK
useConsumer(Case<Weapon>()) // OK
调用:
T
这在消费时限制更少并且更可重用,但我们的类变成了只写。
T
,禁止子类型当你声明一个没有任何变化修饰符的泛型类型时,它被称为invariant。不变量是 T
的生产者和消费者,这意味着函数可以将 T
作为参数,也可以返回 class Case<T> {
private val contents = mutableListOf<T>()
fun produce(): T = contents.last() // Producer: OK
fun consume(item: T) = contents.add(item) // Consumer: OK
}
:
Case
未声明 in
或 out
修饰符的 T
产生和消耗 fun useProducerConsumer(case: Case<Rifle>) {
// Produces Rifle and its subtypes
case.produce()
// Consumes Rifle and its subtypes
case.consume(SniperRifle())
}
及其子类型:
in
如果没有 out
或 Case<Weapon>
修饰符,子类型不允许,所以现在 Case<SniperRifle>
和 Case<Rifle>
都不是 {{ 的子类型1}}。因此,useProducerConsumer()
函数只能用 Case<Rifle>
:
useProducerConsumer(Case<SniperRifle>()) // Error
useProducerConsumer(Case<Rifle>()) // OK
useProducerConsumer(Case<Weapon>()) // Error
这在生产和消费时限制性更强并且可重用性更低,但我们可以读写。
Kotlin 中的 List
只是一个生产者。因为它是使用 out
修饰符声明的:List<out T>
。这意味着您不能向其中添加元素,因为 add(element: T)
是一个使用者函数。每当您希望能够同时使用 get()
和 add()
元素时,请使用不变版本 MutableList<T>
。
就是这样!希望这有助于理解方差的 in
和 out
!
答案 1 :(得分:32)
List<out T> is like List<? extends T> in Java
和
List<in T> is like List<? super T> in Java
例如在Kotlin你可以做像
这样的事情 val value : List<Any> = listOf(1,2,3)
//since List signature is List<out T> in Kotlin
答案 2 :(得分:30)
有了这个签名:
List<out T>
你可以这样做:
val doubleList: List<Double> = listOf(1.0, 2.0)
val numberList: List<Number> = doubleList
表示 T 是协变:
当类 C 的类型参数 T 被声明为 out 时, C&lt; Base&gt; 可以安全地是 C&lt; Derived&gt; 的超类型。
这与中的形成鲜明对比,例如
Comparable<in T>
你可以这样做:
fun foo(numberComparable: Comparable<Number>) {
val doubleComparable: Comparable<Double> = numberComparable
// ...
}
表示 T 逆变:
当 C 的类型参数 T 在中声明时, C&lt; Derived&gt; 可以安全地是 C&lt; Base&gt; 的超类型。
记住它的另一种方法:
消费者 ,生产者输出。
-----------------于2019年1月4日更新----------------- < / p>
对于&#34; Consumer in,Producer out &#34;,我们只读取Producer - call方法获取类型T的结果;并且只能通过传入类型为T的参数来写入Consumer - call方法。
在List<out T>
的示例中,很明显我们可以这样做:
val n1: Number = numberList[0]
val n2: Number = doubleList[0]
因此,在预计List<Double>
时提供List<Number>
是安全的,因此List<Number>
是List<Double>
的超级类型,但反之亦然。
在Comparable<in T>
的示例中:
val double: Double = 1.0
doubleComparable.compareTo(double)
numberComparable.compareTo(double)
因此,在预计Comparable<Number>
时提供Comparable<Double>
是安全的,因此Comparable<Double>
是Comparable<Number>
的超级类型,但反之亦然。
答案 3 :(得分:10)
这些答案解释了什么 out
的作用,但不是为什么您需要它,所以让我们假设我们根本没有 out
.想象一下三个类:Animal、Cat、Dog,以及一个包含 Animal
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") }
}
由于 Dog
是 Animal
的子类型,我们希望使用 List<Dog>
作为 List<Animal>
的子类型,这意味着我们希望能够做到这一点:< /p>
fun allSpeak(animals: List<Animal>) {
animals.forEach { it.speak() }
}
fun main() {
val dogs: List<Dog> = listOf(Dog(), Dog())
allSpeak(dogs)
val mixed: List<Animal> = listOf(Dog(), Cat())
allSpeak(mixed)
}
没关系,代码会为狗打印 woof woof
,为混合列表打印 woof meow
。
问题是当我们有一个可变容器时。由于 List<Animal>
可以包含 Dog
和 Cat
,我们可以将其中之一添加到 MutableList<Animal>
fun processAnimals(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())
processAnimals(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?
}
您不能安全地将 MutableList<Dog>
视为 MutableList<Animal>
的子类型,因为您可以对后者做一些事情(插入一只猫),而对前者却做不到。
举一个更极端的例子:
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")
问题仅在添加到列表时出现,而不是从中读取。将 List<Dog>
用作 List<Animal>
是安全的,因为您不能附加到 List
。这就是 out
告诉我们的。 out
表示“这是我输出的类型,但我不会将其作为我使用的新输入”
答案 4 :(得分:5)
Kotlin
List<out T>
类型是一个只读的接口 像大小,获取等操作。就像在Java中一样,它继承自Collection<T>
,然后继承自Iterable<T>
。方法 更改列表由MutableList<T>
接口添加。这个 模式也适用于Set<out T>/MutableSet<T>
和Map<K, out
V>/MutableMap<K, V>
而且,
在Kotlin,有一种方法可以解释这类事情 编译器。这称为声明站点方差:我们可以注释 Source的类型参数T,以确保仅返回它 (生产)来自
Source<T>
的成员,从未消费过。去做这个 我们提供了out修饰符:> 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 > // ... }
一般规则是:声明类
T
的类型参数C
时 out,它可能只发生在C
成员的外部位置,但在。{ returnC<Base>
可以安全地成为C<Derived>
的超类型。在“聪明的话”中,他们说班级
C
是协变的 参数T
,或T
是协变类型参数。你可以想到 C是T的生产者,而不是T
的消费者。 out修饰符称为方差注释,因为它是 在类型参数声明站点提供,我们谈谈 申报地点差异。这与Java的使用站点形成对比 方差,类型用法中的通配符使类型变为协变。
答案 5 :(得分:2)
记住这样:
in
是“用于输入的”-您想在其中放入(写入)某些东西(因此它是“消费者”)
out
是“用于输出输出”的-您想从中读取(读取)某物(因此它是“生产者”)
如果您来自Java,
<in T>
用于输入,因此就像<? super T>
(消费者)
<out T>
用于输出,因此就像<? extends T>
(生产者)