【发布时间】:2017-06-01 04:44:13
【问题描述】:
我看不懂,在kotlin中找不到out关键字的意思。
您可以在这里查看示例:
List<out T>
如果有人能解释一下这个意思。将不胜感激。
【问题讨论】:
我看不懂,在kotlin中找不到out关键字的意思。
您可以在这里查看示例:
List<out T>
如果有人能解释一下这个意思。将不胜感激。
【问题讨论】:
Kotlin 中的List<out T> 等效于 Java 中的 List<? extends T>。
Kotlin 中的List<in T> 等价于 Java 中的 List<? super T>
例如,在 Kotlin 中,您可以执行以下操作
val value : List<Any> = listOf(1,2,3)
//since List signature is List<out T> in Kotlin
【讨论】:
有了这个签名:
List<out T>
你可以这样做:
val doubleList: List<Double> = listOf(1.0, 2.0)
val numberList: List<Number> = doubleList
这意味着 T 是 协变的:
当 C 类的类型参数 T 声明为 out 时,C
可以安全地是 C 的超类型。
这与 in 形成对比,例如
Comparable<in T>
你可以这样做:
fun foo(numberComparable: Comparable<Number>) {
val doubleComparable: Comparable<Double> = numberComparable
// ...
}
这意味着T是逆变的:
当C类的类型参数T被声明in时,C
可以安全地是 C 的超类型。
另一种记忆方式:
消费者进入,生产者退出。
--2019 年 1 月 4 日更新--
对于“Consumer in, Producer out”,我们只从Producer中读取——调用方法获取T类型的结果;并且仅通过传入 T 类型的参数写入消费者 - 调用方法。
在List<out T>的例子中,很明显我们可以这样做:
val n1: Number = numberList[0]
val n2: Number = doubleList[0]
因此,当需要List<Number> 时提供List<Double> 是安全的,因此List<Number> 是List<Double> 的超类型,但反之则不然。
在Comparable<in T> 的示例中:
val double: Double = 1.0
doubleComparable.compareTo(double)
numberComparable.compareTo(double)
所以当需要Comparable<Double> 时提供Comparable<Number> 是安全的,因此Comparable<Double> 是Comparable<Number> 的超类型,但反之则不然。
【讨论】:
List<out T> 声明的人来说最重要的一点是out 使其不可变(与可变集合相比,后者没有输出)。在答案中提及和强调这一点可能会有所帮助。隐式转换是这一点的结果,而不是要点(因为不能写入 Listout 部分并不是使 List 不可变的原因。您可以轻松创建自己的具有 clear() 方法的 List<out T> 接口,因为它不需要任何参数。
out 映射到生产者,in 映射到消费者。
Collection<out E> 非常令人困惑。事实上,如果Collection 也接受E 作为输入参数,那么为什么Collection 可以成为E 的生产者(参见::contains(E))。诀窍是@UnsafeVariance 注释。但是没有地方提到这一点,你必须通过源代码来获取它并说......“oooohhhh”!!!
方差修饰符 out 和 in 允许我们通过允许子类型来减少泛型类型的限制和更可重用。
让我们借助对比示例来理解这一点。我们将使用案例作为各种武器的容器。假设我们有以下类型层次结构:
open class Weapon
open class Rifle : Weapon()
class SniperRifle : Rifle()
out 产生 T 并保留子类型当您使用out 修饰符声明泛型类型时,它称为协变。协变是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
}
使用out 修饰符声明的Case 产生T 及其子类型:
fun useProducer(case: Case<Rifle>) {
// Produces Rifle and its subtypes
val rifle = case.produce()
}
使用out 修饰符,子类型保留,因此SniperRifle 是Rifle 的子类型时Case<SniperRifle> 是Case<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
}
使用in 修饰符声明的Case 消耗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,不允许子类型化当你声明一个没有任何变体修饰符的泛型类型时,它被称为invariant。不变量是T 的生产者和消费者,这意味着函数可以将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
}
没有in 或out 修饰符声明的Case 产生并使用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> 的子类型。结果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>。
就是这样!希望这有助于理解ins 和outs 的差异!
【讨论】:
这些答案解释了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> 的子类型,这意味着我们希望能够做到这一点:
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 说“这是我输出的一种类型,但我不把它当作我消费的新输入”
【讨论】:
out,那么尝试调用allSpeak(mixed) 将无法编译。您只能使用 List<Animal> 调用它
List 被声明为 List<out T>。您是否想知道in 为您提供什么安全性?
out和in。你能用out和in给出同样的例子吗?
List 确实使用out,所以allSpeak 有效且安全
这样记住:
in 是“for input”——你想在里面放(写)一些东西(所以它是一个“消费者”)
out 是“for output” - 你想从中获取(读取)一些东西(所以它是一个“生产者”)
如果你来自 Java,
<in T> 用于输入,所以它就像<? super T>(消费者)
<out T> 用于输出,所以就像<? extends T>(生产者)
【讨论】:
Kotlin
List<out T>类型是一个提供只读的接口 大小,获取等操作。就像在 Java 中一样,它继承自Collection<T>又继承自Iterable<T>。方法 更改列表由MutableList<T>接口添加。这 模式也适用于Set<out T>/MutableSet<T>和Map<K, outV>/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 > // ... }一般规则是:当声明类
C的类型参数T时 out,它可能只出现在C的成员中,但在 returnC<Base>可以安全地成为C<Derived>的超类型。用“聪明的话”他们说
C类在 参数T,或者T是协变类型参数。你可以想到 C 是 T 的生产者,而不是T的消费者。 out 修饰符称为方差注释,因为它是 在类型参数声明站点提供,我们讲讲 声明站点差异。这与 Java 的使用站点相反 类型使用中的通配符使类型协变的差异。
【讨论】:
来自Functional programming in Kotlin的一个很好的解释:
考虑以下代码:
sealed class List<out A>
object Nil : List<Nothing>()
data class Cons<out A>(val head: A, val tail: List<A>) : List<A>()
在声明类 List 中,类型参数 A 前面的 out 是方差注释,表明 A 是 List 的协变或“正”参数。例如,这意味着 List 被认为是 List 的子类型,假设 Dog 是 Animal 的子类型。 (更一般地说,对于所有类型 X 和 Y,如果 X 是 Y 的子类型,则 List 是 List 的子类型。)我们可以省略 A 前面的 out,这将使 List 在该类型参数中保持不变。 但请注意,现在 Nil 扩展了 List。 Nothing 是所有类型的子类型,这意味着结合方差注释,Nil 可以被视为 List、List 等等,完全符合我们的要求。
【讨论】: