当作为参数传递时,输入输出在kotlin中实际上起什么作用?

时间:2019-04-14 17:03:23

标签: generics kotlin

我浏览了kotlin文档,博客和stackoverflow帖子,以了解 in out kotlin 中。我在这里发现理论是,消费者(收进),生产者(退回)。

但是下面的两个方法如何区分输入和输出的方法参数,在这里我们可以毫无问题地访问“列表”变量。

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

}

private fun exampleMethod2(list: ArrayList<in String>) {

}

4 个答案:

答案 0 :(得分:6)

类型投影

在函数参数中使用修饰符inout时,称为类型投影

<头>
预测 产生 消耗 函数行为
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>、{{1} },因为 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 } 产生 producer 及其子类型。此处 Orange 可以返回 producer.get(1)MandarinOrange 等,但我们不在乎,只要我们得到 BloodOrange。因为我们只对在 use-site 使用 Orange 的属性和函数感兴趣。

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


ArrayList<MandarinOrange> 消耗 in 并且函数接受超类型

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

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 } 消耗 consumer 及其子类型。是 Orange 还是 MandarinOrange 都没有关系,只要它是 BloodOrange。因为 Orange 只对其声明处的 consumer 的属性和功能感兴趣。

编译器确实允许调用 Orange 函数(生产者),但它产生的 get() 对我们没有用。当您在使用站点将该 Any? 用作 Any? 时,编译器会标记错误。


Invariant 产生和消耗 Orange,函数不接受子类型或超类型

当您使用 T 作为生产者和消费者(没有 ArrayListin)时,该函数只能接受确切类型 out,不能接受其他子类型,例如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 } 及其子类型。


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

答案 1 :(得分:2)

对于 in 泛型,我们可以将 super-type 类分配给 sub-type 类。 但是对于 out 泛型,我们可以将 sub-type 类分配给 super-type

答案 2 :(得分:0)

这是关于协变和逆变的。当您说函数接受List<out T>时,表示它可以接受List<T>或从T继承的按类键入的任何List。另一方面,具有List<in T>的函数可以接受List<T>以及由T超类键入的任何List。

答案 3 :(得分:0)

让我通过示例演示输入/输出的功能。请考虑以下内容:

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

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

现在,我们尝试将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>())

但是我们会出错!我们该如何解决?我们必须以某种方式告诉编译器,对于foo列表也可以包含Number子类型的元素(例如Int),对于bar我们必须告诉编译器编译器,该列表还可以包含基本类型为Number的元素(例如Any)。

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

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

现在可以使用了!

Further reading