Kotlin 的 out 关键词是什么

我不能理解,我不能找到的意义 出去关键字在科特林。

你可以在这里查看例子:

List<out T>

如果有人能解释一下这是什么意思,我将不胜感激。

48729 次浏览

参考 Kotlin 手册

Kotlin List<out T>类型是一个只提供读的接口 像 size,get 等操作。像 Java 一样,它继承自 Collection<T>继承自 Iterable<T> 更改列表是由 MutableList<T>接口添加的 模式也适用于 Set<out T>/MutableSet<T>Map<K, out V>/MutableMap<K, V>

还有这个,

在 Kotlin,有一种方法可以解释这类事情 这就是所谓的声明-站点方差: 我们可以注释 Source 的 type 参数 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时 外,它可能只发生在外部位置的 C成员,但在 返回 C<Base>可以安全地成为 C<Derived>的超类型。

在“聪明的话”中,他们说类 C在 参数 T,或者 T是一个协变类型参数 作为 T 的生产商,而不是消费者的 T的。 Out 修饰符称为方差注释,因为它是 提供的类型参数声明站点,我们将讨论 声明-站点方差。这与 Java 的使用-站点相反 其中类型使用中的通配符使类型协变。

有这个签名:

List<out T>

你可以这样做:

val doubleList: List<Double> = listOf(1.0, 2.0)
val numberList: List<Number> = doubleList

这意味着 T协变体:

当类 C的类型参数 T被声明为 出去时,C < 基地 > 可以安全地成为 C < 衍生 > 超模

这是与 进去的对比,例如。

Comparable<in T>

你可以这样做:

fun foo(numberComparable: Comparable<Number>) {
val doubleComparable: Comparable<Double> = numberComparable
// ...
}

这意味着 T反变量:

当类 C的类型参数 T被声明为 进去时,C < 衍生 > 可以安全地成为 C < 基地 > 超模

另一种方式来记住它:

消费者 进去,生产者 出去

Kotlin 泛型方差

————————更新于2019年1月4日——————————

对于“ 消费者进,生产者出”,我们只读取 Producer-call 方法以获得 T 类型的结果; 并且只通过传入 T 类型的参数来写入 Consumer-call 方法。

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>的超级类型,但反之亦然。

在 Kotlin 的 List<out T>相当于在爪哇的 List<? extends T>

在 Kotlin 的 List<in T>相当于在爪哇的 List<? super T>

例如在 Kotlin,你可以这样做

val value : List<Any> = listOf(1,2,3)
//since List signature is List<out T> in Kotlin

这样做的原因是,如果你返回它,你可以标记出通用的“ out”,但是从来没有收到过。如果你收到了,你可以把它标记为“ in”,但是永远不要回来。

记住:

in是“ for 进去put”-你想把(写)什么东西放进去(所以它是一个“消费者”)

out是“ for 出去put”-你想从中取出(读出)一些东西(所以它是一个“制片人”)

如果你来自爪哇,

<in T>用于输入,因此它类似于 <? super T>(消费者)

<out T>是用于输出的,因此它类似于 <? extends T>(生产商)

方差修饰符 outin允许我们通过允许子类型来降低泛型类型的限制性和提高其可重用性。

让我们通过对比例子来理解这一点。我们将使用案例作为各种武器的容器。假设我们有以下类型层次结构:

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修饰符,子类型是 保存完好,所以当 SniperRifleRifle的子类型时,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>的子类型,而 RifleWeapon的子类型。因此,useConsumer()函数也可以用 Case<Weapon>调用:

useConsumer(Case<SniperRifle>())               // Error
useConsumer(Case<Rifle>())                     // OK
useConsumer(Case<Weapon>())                    // OK

这是 没那么严格更可重复使用同时消费,但我们的类成为 只写


不变量生成并使用 T不允许子类型

当您声明一个没有任何方差修饰符的泛型类型时,它被称为 不变。不变量是 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
}

没有 inout修饰符的 Case产生并消耗 T及其子类型:

fun useProducerConsumer(case: Case<Rifle>) {
// Produces Rifle and its subtypes
case.produce()
// Consumes Rifle and its subtypes
case.consume(SniperRifle())
}

没有 inout修饰符,子类型是 不允许,所以现在 Case<Weapon>Case<SniperRifle>都不是 Case<Rifle>的子类型。因此,只能用 Case<Rifle>调用 useProducerConsumer()函数:

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 和 out的差异!

这些答案解释了 什么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") }
}

因为 DogAnimal的一个子类型,我们希望使用 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>可以包含 DogCat,所以我们可以将其中任何一个添加到 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说“这是一个我输出的类型,但是我不把它当作我使用的新输入”

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 的协变量或“正”参数。这意味着,例如,假设 Dog 是 Animal 的一个子类型,List 被认为是 List 的一个子类型。(更一般地说,对于所有类型 X 和 Y,如果 X 是 Y 的子类型,则 List 是 List 的子类型。)我们可以省略 A 前面的 out,这将使 List 在该类型参数中保持不变。 但注意,现在 Nil 扩展了 List。Nothing 是所有类型的子类型,这意味着结合方差注释,Nil 可以被认为是 List、 List 等等,正如我们所希望的那样。

生产 = 产出 = 出口。

interface Production<out T> {
fun produce(): T
}

如果泛型类只使用泛型类型作为其函数/s 的输出,则使用 out

消费 = 输入 = 输入。

   interface Consumer<in T> {
fun consume(item: T)
}

如果泛型类只使用泛型类型作为其函数/s 的输入,则使用 in

请参阅 Https://medium.com/mobile-app-development-publication/in-and-out-type-variant-of-kotlin-587e4fa2944c 详细解释。

你可以把它当作:

  • 放大-> T 继承的任何类/接口
  • 输出 缩小-> 任何继承了 T 的类

就这么简单。