我浏览了kotlin文档,博客和stackoverflow帖子,以了解 in 和 out 在 kotlin 中。我在这里发现理论是,消费者(收进),生产者(退回)。
但是下面的两个方法如何区分输入和输出的方法参数,在这里我们可以毫无问题地访问“列表”变量。
private fun exampleMethod1(list: ArrayList<out String>) {
}
private fun exampleMethod2(list: ArrayList<in String>) {
}
答案 0 :(得分:6)
在函数参数中使用修饰符in
或out
时,称为类型投影。
预测 | 产生 | 消耗 | 函数行为 |
---|---|---|---|
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
),该怎么办?不用担心意外使用其他不需要的功能?
在这种情况下,我们使用方差修饰符 in
和 out
来投影类型
在使用现场。 Use-site 只是意味着我们在哪里使用 ArrayList<T>
类。
out
产生 T
并且函数接受子类型当您使用ArrayList
作为生产者(out
)时,该函数可以接受ArrayList<Orange>
的子类型,即ArrayList<MandarinOrange>
、{{1} },因为 ArrayList<BloodOrange>
和 MandarinOrange
是 BloodOrange
的子类型。因为保留了子类型:
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?
时,编译器会标记错误。
Orange
,函数不接受子类型或超类型当您使用 T
作为生产者和消费者(没有 ArrayList
或 in
)时,该函数只能接受确切类型 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>) {}
现在可以使用了!