当作为参数传递时,in/out 在 Kotlin 中实际上做了什么?

cha*_*nae 6 generics kotlin

我想了解inout在 Kotlin 中。正如我发现的理论一样,消费者in需要和生产者out回报。

但是,下面的两种方法如何区分何时inout被视为我们可以list毫无问题地访问的方法参数?

private fun exampleMethod1(list: ArrayList<out String>) {}

private fun exampleMethod2(list: ArrayList<in String>) {}
Run Code Online (Sandbox Code Playgroud)

Wil*_*zel 16

让我通过一个例子来演示什么in/out做什么。考虑以下:

private fun foo(list: ArrayList<Number>) {}

private fun bar(list: ArrayList<Number>) {}
Run Code Online (Sandbox Code Playgroud)

现在我们尝试向ArrayList每个函数传递一个,每个函数都有一个不同的泛型类型参数:

// Error: Type Mismatch. Required `ArrayList<Number>` Found `ArrayList<Int>`
foo(arrayListOf<Int>())

// Error: Type Mismatch. Required `ArrayList<Number>` Found `ArrayList<Any>`
bar(arrayListOf<Any>())
Run Code Online (Sandbox Code Playgroud)

但是我们得到了错误!我们如何解决这个问题?我们必须以某种方式告诉编译器,因为foo列表还可以包含Number(eg Int)的子类型的元素,因为bar我们必须告诉编译器列表还可以包含Number(eg Any)的基本类型的元素。

private fun foo(list: ArrayList<out Number>) {}

private fun bar(list: ArrayList<in Number>) {}
Run Code Online (Sandbox Code Playgroud)

现在它起作用了!

进一步阅读


Yog*_*ity 8

类型投影

当修饰符inorout用于函数参数时,称为类型投影

预测 生产 消耗 函数行为
ArrayList<out Orange> Orange Nothing 接受子类型 ArrayList<Orange>
ArrayList<in Orange> Any? Orange 接受超类型 ArrayList<Orange>
ArrayList<Orange> Orange Orange 不接受任何子类型或超类型

ArrayList在 Kotlin 中既是生产者也是消费者。这是因为它是一个不变的泛型类,定义为ArrayList<T>,而不是ArrayList<out T>(生产者)或ArrayList<in T>(消费者)。这意味着该类可以具有接受T作为函数参数(消费)或返回T(生产)的函数。

但是,如果您想安全地将这个已经存在的类用作消费者(in T)或生产者(out T),该怎么办?无需担心意外使用其他不需要的功能?

在这种情况下,我们通过使用差异修饰符inout 在使用地点来投影类型。Use-site只是意味着我们在哪里使用这个ArrayList<T>类。


out产生T并且函数接受子类型

当您将ArrayList用作生产者( out) 时,该函数可以接受 的子类型ArrayList<Orange>,即ArrayList<MandarinOrange>, ArrayList<BloodOrange>,因为MandarinOrangeBloodOrange是 的子类型Orange。因为保留了子类型:

fun useAsProducer(producer: ArrayList<out Orange>) {

    // Producer produces Orange and its subtypes
    val orange = producer.get(1)            // OK

    // Can use functions and properties of Orange
    orange.getVitaminC()                    // OK

    // Consumer functions not allowed
    producer.add(BloodOrange())             // Error
}
Run Code Online (Sandbox Code Playgroud)

producer生产Orange和它的亚型。在这里producer.get(1)可以返回MandarinOrangeBloodOrange等等。但我们不只要我们得到的关心Orange。因为我们只对使用Orangeat use-site的属性和功能感兴趣。

编译器不允许调用add()函数(消费者),因为我们不知道Orange它包含哪种类型。您不想意外添加BloodOrange,如果这是一个ArrayList<MandarinOrange>.


in消耗T并且函数接受超类型

当你ArrayList作为消费者(in)使用时,该函数可以接受 的超类型ArrayList<Orange>,也就是说,ArrayList<Fruit>因为现在子类型被颠倒了。这意味着ArrayList<Fruit>ArrayList<Orange>whenOrange的子类型是 的子类型Fruit

fun useAsConsumer(consumer: ArrayList<in Orange>) {

    // Produces Any?, no guarantee of Orange because this could
    // be an ArrayList<Fruit> with apples in it
    val anyNullable = consumer.get(1)       // Not useful

    // Not safe to call functions of Orange on the produced items.
    anyNullable.getVitaminC()               // Error

    // Consumer consumes Orange and its subtypes
    consumer.add(MandarinOrange())          // OK
}
Run Code Online (Sandbox Code Playgroud)

consumer消耗Orange它的亚型。是 aMandarinOrange还是 a都没有关系BloodOrange,只要它是Orange. 因为consumerOrange对其声明站点的属性和功能感兴趣。

编译器确实允许调用get()函数(生产者),但它产生Any?对我们没有用的。当您将其Any?用作Orangeat use-site时,编译器会标记错误。


不变的产生和消耗T,函数不接受子类型或超类型

当您ArrayList用作生产者和消费者(没有inout)时,该函数只能接受确切的类型ArrayList<Orange>,而不能接受其他子类型(如 )ArrayList<MandarinOrange>或超类型(如 )ArrayList<Fruit>。因为不变量不允许子类型化:

fun useAsProducerConsumer(producerConsumer: ArrayList<Orange>) {
    // Produces Orange and its subtypes
    val orange = producerConsumer.get(1)    // OK

    // Orange is guaranteed
    orange.getVitaminC()                    // OK

    // Consumes Orange and its subtypes
    producerConsumer.add(Orange())          // OK
}
Run Code Online (Sandbox Code Playgroud)

不变式生产和消费Orange及其子类型。


就是这样!类型投影就是告诉编译器您如何在该特定函数中使用该类,因此如果您不小心调用了非预期的函数,它可以通过标记错误来帮助您。希望有帮助。

  • 最初的表格已经解释了很多:) Ty (2认同)