什么是kotlin中的关键字

Aks*_*ood 54 generics kotlin

我无法理解,我无法在kotlin中找到out关键字的含义.

您可以在此查看示例:

List<out T>
Run Code Online (Sandbox Code Playgroud)

如果有人能够解释这个的含义.真的很感激.

Dmi*_*din 53

List<out T> is like List<? extends T> in Java
Run Code Online (Sandbox Code Playgroud)

List<in T> is like List<? super T> in Java
Run Code Online (Sandbox Code Playgroud)

例如在Kotlin你可以做类似的事情

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

  • 没有比这更容易解释的方法了。我立即将其理解为`?扩展 T` 和 `? 在我看来,super T` 比 `out T` 和 `in T` 更容易理解。 (8认同)

Yog*_*ity 51

方差改性剂outin允许我们做出允许亚型我们的泛型类型限制更少,更可重复使用。

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

open class Weapon
open class Rifle : Weapon()
class SniperRifle : Rifle()
Run Code Online (Sandbox Code Playgroud)

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
}
Run Code Online (Sandbox Code Playgroud)

Case与声明的out修改产生T及其亚型:

fun useProducer(case: Case<Rifle>) {
    // Produces Rifle and its subtypes
    val rifle = case.produce()
}
Run Code Online (Sandbox Code Playgroud)

使用out修饰符,子类型被保留,所以Case<SniperRifle>is 的子类型Case<Rifle>whenSniperRifle是 的子类型Rifle。因此,该useProducer()函数也可以调用Case<SniperRifle>

useProducer(Case<SniperRifle>())               // OK
useProducer(Case<Rifle>)                       // OK
useProducer(Case<Weapon>())                    // Error
Run Code Online (Sandbox Code Playgroud)

这在生产时限制较少可重用,但我们的类变为只读


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
}
Run Code Online (Sandbox Code Playgroud)

Case与声明的in修改消耗T及其亚型:

fun useConsumer(case: Case<Rifle>) {
    // Consumes Rifle and its subtypes
    case.consume(SniperRifle())
}
Run Code Online (Sandbox Code Playgroud)

使用in修饰符,子类型被反转,所以现在Case<Weapon>is a subtype Case<Rifle>when Rifleis a subtype of Weapon。因此,该useConsumer()函数也可以调用Case<Weapon>

useConsumer(Case<SniperRifle>())               // Error          
useConsumer(Case<Rifle>())                     // OK
useConsumer(Case<Weapon>())                    // OK
Run Code Online (Sandbox Code Playgroud)

这是限制较少更加可重复使用而消耗,但我们班变成只写


不变的生产和消费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
}
Run Code Online (Sandbox Code Playgroud)

Case无申报inout修改生产和消费T及其亚型:

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

没有inorout修饰符,子类型是不允许的,所以现在既不是Case<Weapon>也不Case<SniperRifle>是 的子类型Case<Rifle>。因此,useProducerConsumer()只能使用以下命令调用该函数Case<Rifle>

useProducerConsumer(Case<SniperRifle>())       // Error
useProducerConsumer(Case<Rifle>())             // OK
useProducerConsumer(Case<Weapon>())            // Error
Run Code Online (Sandbox Code Playgroud)

这在生产和消费时更具限制性更少的可重用性,但我们可以读取和写入.


结论

List在科特林只是一个生产商。因为它是使用out修饰符声明的:List<out T>。这意味着您不能向其中添加元素,因为它add(element: T)是一个使用者函数。无论何时您希望能够get()add()元素一样,请使用不变版本MutableList<T>

就是这样!希望这有助于理解方差的ins 和outs!

  • 这应该是公认的答案。 (5认同)
  • 这是最清晰的解释之一(特别是对于像泛型这样的复杂主题)。向你致敬! (2认同)

And*_*eng 43

有这个签名:

List<out T>
Run Code Online (Sandbox Code Playgroud)

你可以这样做:

val doubleList: List<Double> = listOf(1.0, 2.0)
val numberList: List<Number> = doubleList
Run Code Online (Sandbox Code Playgroud)

这意味着T协变的:

当类型参数Ť一类Ç被声明出来,ç<数据库>可以安全地是一个超型ç<派生>.

这是相反,如

Comparable<in T>
Run Code Online (Sandbox Code Playgroud)

你可以这样做:

fun foo(numberComparable: Comparable<Number>) {
  val doubleComparable: Comparable<Double> = numberComparable
  // ...
}
Run Code Online (Sandbox Code Playgroud)

这意味着T逆变的:

当类型参数Ť一类Ç被声明,ç<派生>可以安全地是一个超型ç<数据库>.

记住它的另一种方法:

消费者,制作出来.

参见Kotlin Generics Variance

----------------- 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]
Run Code Online (Sandbox Code Playgroud)

因此,List<Double>List<Number>预期时提供是安全的,因此List<Number>是超类型List<Double>,但反之亦然.

在示例中Comparable<in T>:

val double: Double = 1.0
doubleComparable.compareTo(double)
numberComparable.compareTo(double)
Run Code Online (Sandbox Code Playgroud)

因此,Comparable<Number>Comparable<Double>预期时提供是安全的,因此Comparable<Double>是超类型Comparable<Number>,但反之亦然.

  • 对不起,但仍然无法理解. (8认同)
  • 我认为看到 `List&lt;out T&gt;` 声明的最重要的一点是 `out` 使其不可变(与没有 out 的可变集合相比)。在答案中提及和强调这一点可能会有所帮助。隐式转换是这一点的结果,而不是要点(因为不能写入 List&lt;Number&gt;,可以安全地将其作为对 List&lt;Double&gt; 的引用)。 (2认同)
  • @minsk`out`部分不是使`List`不可变的原因。您可以轻松地创建自己的具有`clear()`方法的`List &lt;out T&gt;`接口,因为它不需要任何参数。 (2认同)

Rya*_*ing 14

这些答案解释了什么 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") }
}
Run Code Online (Sandbox Code Playgroud)

由于 aDog是 的子类型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)
}
Run Code Online (Sandbox Code Playgroud)

没关系,代码将为woof woof狗和woof meow混合列表打印。

问题是当我们有一个可变容器时。由于 aList<Animal>可以包含Dogand Cat,我们可以将其中一个添加到 aMutableList<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?
}
Run Code Online (Sandbox Code Playgroud)

您不能安全地将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")
Run Code Online (Sandbox Code Playgroud)

问题仅在添加到列表时发生,而不是从中读取。使用List<Dog>as是安全的,List<Animal>因为您不能附加到List. 这就是out告诉我们的。out说“这是我输出的类型,但我不把它作为我消费的新输入”


sta*_*iet 7

像这样记住:

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

out是“为了出去” - 你想从中取出(读取)一些东西(所以它是一个“生产者”)

如果你来自爪哇,

<in T>用于输入,所以它就像<? super T>(消费者)

<out T>用于输出,所以就像<? extends T>(生产者)


Mar*_*Gin 7

生产=输出=输出。

\n
interface Production<out T> {\n    fun produce(): T\n}\n
Run Code Online (Sandbox Code Playgroud)\n

如果您的泛型类仅使用泛型类型作为 it\xe2\x80\x99s 函数的输出,则使用 out

\n

消耗=输入=输入。

\n
   interface Consumer<in T> {\n    fun consume(item: T)\n}\n
Run Code Online (Sandbox Code Playgroud)\n

如果您的泛型类仅使用泛型类型作为 it\xe2\x80\x99s 函数的输入,则使用 in

\n

请参阅\n https://medium.com/mobile-app-development-publication/in-and-out-type-variant-of-kotlin-587e4fa2944c\n了解详细说明。

\n


Kri*_*ofe 5

请参阅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的类型参数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
>     // ... }
Run Code Online (Sandbox Code Playgroud)

一般规则是:当声明T一个类的类型参数C时,它可能只发生在成员的外部位置C,但作为回报C<Base>可以安全地成为超类型C<Derived>.

在"聪明的话"中,他们说该类C在参数中是协变的T,或者T是协变类型参数.您可以将C视为T的生产者,而不是消费者T.out修饰符称为方差注释,因为它是在类型参数声明站点提供的,所以我们讨论声明站点方差.这与Java的使用站点差异形成对比,其中类型用法中的通配符使类型变为协变.