为什么在 "out" 位置考虑函数参数中的逆变类型参数?

Why are contravariant type parameters in function parameters considered in "out" position?

我很难用英语描述,但问题是:

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> 似乎并不安全。有人可以解释一下吗?

参数位置称为逆变,因为它的方差方向相反w.r.t。 class 方差。这意味着 class 的超类型可以将参数类型的子类型作为参数,反之亦然。

让我们考虑一些实际的参数类型S。在此示例中,类型 Accepter<S>Accepter<Any> 的超类型,它必须采用 Consumer<Any> 子类型 作为参数,但使用给定的它采用 Consumer<S> 的签名,它不是 Consumer<Any> 的子类型,而是它的超类型。

另一个例子说明如果允许这种参数类型是不安全的。让我们考虑以下 AccepterConsumer 的实现:

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)

本身允许输入的函数参数在逻辑上等同于函数的return值,显然在"out"位置。

考虑这个简单的例子:

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() 将只允许使用消费者必须能够使用的 T 的某些特定子类型调用 Consumer。例如,假设 work 接受了另一个参数,就像在原始问题中一样,并且实际上做了一些事情:

class Worker<in T> {
    private val inputs = mutableListOf<T>()

    fun <U : T> work(input: U, output: Consumer<U>) {
        inputs += input
        output.accept(input)
    }
}

通过引入类型参数U,我们可以确保inputoutput彼此一致,但仍然允许Worker<Any>扩展Worker<String>.