Kotlin中的out关键字是什么

pxq42qpu  于 2023-06-24  发布在  Kotlin
关注(0)|答案(9)|浏览(109)

我无法理解,也无法在Kotlin中找到out关键字的含义。
你可以在这里查看示例:

List<out T>

如果有人能解释这是什么意思。我会很感激的

qnakjoqk

qnakjoqk1#

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

理由是你可以标记通用“出”,如果你返回它,但永远不会收到。如果你收到了,你可以把它标记为“in”,但永远不要回来。

i2loujxw

i2loujxw2#

变量修饰符outin允许我们通过子类型化来减少泛型类型的限制,提高泛型类型的可重用性。
让我们借助对比的例子来理解这一点。我们将使用案例作为各种武器的容器。假设我们有以下类型层次结构:

open class Weapon
open class Rifle : Weapon()
class SniperRifle : Rifle()

out生成T并保留子类型

当你用out修饰符声明一个泛型类型时,它被称为 * 协变 *。协变量是T的 * producer *,这意味着函数可以返回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

这在消费时 * 限制更少 * 并且 * 更可重用 *,但是我们的类变成了 * 只写 *。

Invariant产生并消耗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
}

在没有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>的子类型。因此,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 s和out s!

wztqucjr

wztqucjr3#

使用此签名:

List<out T>

你可以这样做:

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

这意味着T协变
当类C的类型参数T被声明为out时,C可以安全地成为C超类型can safely be asupertypeofC .
这与
中的**形成对比,例如

Comparable<in T>

你可以这样做:

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

这意味着T逆变
当类C的类型参数T中声明时,C可以安全地成为C超类型**。can safely be asupertypeofC .
另一种方式来记住它:
消费者
进**,生产者
参见Kotlin泛型方差

  • -----------------更新于2019年1月4日------------------*

对于“Consumer in,Producer out”,我们只从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>的超类型,但反之亦然。

pwuypxnk

pwuypxnk4#

这些答案解释了**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 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?
}

你不能安全地认为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说“这是我输出的类型,但我不把它作为我消费的新输入”

jk9hmnmh

jk9hmnmh5#

记住这样:
in是“forinput”-你想把东西放进去(写)进去(所以它是一个“消费者”)
out是“foroutput”-你想从中获取(阅读)一些东西(所以它是一个“生产者”)
如果你来自 java
<in T>用于输入,因此它类似于<? super T>(消费者)
<out T>用于输出,因此类似于<? extends T>(生产者)

rmbxnbpk

rmbxnbpk6#

produce = output = out.

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

如果泛型类只使用泛型类型作为它的函数的输出,那么使用out

consume = input = in.

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

如果泛型类只使用泛型类型作为它的函数的输入,那么使用in
详细说明请参考https://medium.com/mobile-app-development-publication/in-and-out-type-variant-of-kotlin-587e4fa2944c

r8xiu3jd

r8xiu3jd7#

参考Kotlin手册
KotlinList<out T>类型是一个提供只读操作的接口,如size,get等。与Java一样,它继承自Collection<T>,而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的成员中的out位置,但反过来C<Base>可以安全地成为C<Derived>的超类型。
在“聪明的话”中,他们说类C在参数T中是协变的,或者T是一个协变类型参数。你可以把C看作是T的生产者,而不是T的消费者。out修饰符被称为方差注解,由于它是在类型参数声明位置提供的,因此我们讨论声明位置的方差。这与Java的使用位置变化形成对比,其中类型使用中的通配符使类型协变。

5lhxktic

5lhxktic8#

您可以将其视为:

  • in -> zoom in -> any classes/interfaces the T inherited from
  • out -> zoom out ->任何继承了T的类

就这么简单

2sbarzqh

2sbarzqh9#

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是所有类型的子类型,这意味着结合variance注解,Nil可以被认为是一个List,一个List,等等,正如我们所希望的那样。

相关问题