什么是kotlin中的关键字

时间:2017-06-01 04:44:13

标签: generics kotlin

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

您可以在此处查看示例:

List<out T>

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

6 个答案:

答案 0 :(得分:38)

差异修饰符 outin 允许我们通过允许子类型化来减少泛型类型的限制并提高可重用性。

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

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> 的子类型时,SniperRifleRifle 的子类型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> 是子类型时,RifleWeapon 的子类型useConsumer()。因此,Case<Weapon> 函数也可以用 useConsumer(Case<SniperRifle>()) // Error useConsumer(Case<Rifle>()) // OK useConsumer(Case<Weapon>()) // OK 调用:

T

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


Invariant 生产和消费 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

未声明 inout 修饰符的 T 产生和消耗 fun useProducerConsumer(case: Case<Rifle>) { // Produces Rifle and its subtypes case.produce() // Consumes Rifle and its subtypes case.consume(SniperRifle()) } 及其子类型:

in

如果没有 outCase<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>

就是这样!希望这有助于理解方差的 inout

答案 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; 超类型

记住它的另一种方法:

  

消费者 ,生产者输出

请参阅Kotlin Generics Variance

-----------------于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") }
}

由于 DogAnimal 的子类型,我们希望使用 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> 可以包含 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 表示“这是我输出的类型,但我不会将其作为我使用的新输入”

答案 4 :(得分:5)

请参阅manual of kotlin

  

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成员的外部位置,但在。{   return C<Base>可以安全地成为C<Derived>的超类型。

     

在“聪明的话”中,他们说班级C是协变的   参数T,或T是协变类型参数。你可以想到   C是T的生产者,而不是T的消费者。   out修饰符称为方差注释,因为它是   在类型参数声明站点提供,我们谈谈   申报地点差异。这与Java的使用站点形成对比   方差,类型用法中的通配符使类型变为协变。

答案 5 :(得分:2)

记住这样:

in是“用于输入的”-您想在其中放入(写入)某些东西(因此它是“消费者”)

out是“用于输出输出”的-您想从中读取(读取)某物(因此它是“生产者”)

如果您来自Java,

<in T>用于输入,因此就像<? super T>(消费者)

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