我很难用英语来描述,但这是问题所在:
class Consumer<in T> {
fun consume(t: T) {}
}
class Accepter<in T>() {
// ERROR: Type parameter T is declared as 'in' but occurs in 'out' position in type Consumer<T>
fun acceptWith(value: T, consumer: Consumer<T>) {}
}
可以这样修复:
fun <U : T> acceptWith(value: T, consumer: Consumer<U>) {}
但是我不明白这个问题。允许Consumer<T>
似乎并不安全。有人可以解释吗?
答案 0 :(得分:2)
自变量位置称为逆变,因为其方差沿相反的方向w.r.t。类差异。这意味着类的超类型可以将参数类型的子类型作为参数,反之亦然。
让我们考虑一些实际的参数类型S
。在此示例中,类型Accepter<S>
是Accepter<Any>
的超类型,必须使用Consumer<Any>
的子类型作为参数,但是使用给定的签名,它将Consumer<S>
,不是Consumer<Any>
的子类型,而是它的超类型。
另一个示例说明为什么允许该参数类型不安全。让我们考虑以下Accepter
和Consumer
的实现:
class AnyAccepter : Accepter<Any>() {
override fun acceptWith(value: Any, consumer: Consumer<Any>) {
consumer.consume(Any())
}
}
class StringConsumer : Consumer<String>() {
override fun consume(t: String) {
println(t.length)
}
}
fun main() {
val anyAccepter = AnyAccepter()
val stringAccepter: Accepter<String> = anyAccepter
// here we're passing a StringConsumer, but the implementation expects Consumer<Any>
stringAccepter.acceptWith("x", StringConsumer())
}
使用这些实现,您将获得一个声音程序,该程序在运行时将导致ClassCastException:
Exception in thread "main" java.lang.ClassCastException: class java.lang.Object cannot be cast to class java.lang.String
at contravariance.StringConsumer.consume(consumers.kt:27)
at contravariance.AnyAccepter.acceptWith(consumers.kt:23)
at contravariance.ConsumersKt.main(consumers.kt:36)
答案 1 :(得分:1)
本身允许输入的函数参数在逻辑上等同于函数的返回值,这些返回值显然处于“出”位置。
考虑以下简单示例:
interface Worker<in T> {
fun work(output: Consumer<T>)
}
从逻辑上讲,它等同于
interface Worker<in T> {
fun work(): T
}
在任何情况下, work()
都可以输出一个值。
此失败的示例:
fun bad(anyWorker: Worker<Any>) {
val stringWorker: Worker<String> = anyWorker
stringWorker.work(Consumer { value: String -> /* value could be Any since it came from anyWorker! */ })
}
但是,我们可以通过为函数引入新的类型参数来解决此问题:
interface Worker<in T> {
fun <U : T> work(output: Consumer<U>)
}
现在,work()
仅被允许使用消费者必须能够消费的Consumer
某些特定子类型来调用T
。例如,让我们想象一下工作像原始问题一样接受了另一个论证,并且实际上做了一些事情:
class Worker<in T> {
private val inputs = mutableListOf<T>()
fun <U : T> work(input: U, output: Consumer<U>) {
inputs += input
output.accept(input)
}
}
通过引入类型参数U
,我们可以确保input
和output
彼此一致,但仍允许Worker<Any>
扩展{{1 }}。