为什么在 "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>
的子类型,而是它的超类型。
另一个例子说明如果允许这种参数类型是不安全的。让我们考虑以下 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)
本身允许输入的函数参数在逻辑上等同于函数的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
,我们可以确保input
和output
彼此一致,但仍然允许Worker<Any>
扩展Worker<String>
.
我很难用英语描述,但问题是:
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>
的子类型,而是它的超类型。
另一个例子说明如果允许这种参数类型是不安全的。让我们考虑以下 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)
本身允许输入的函数参数在逻辑上等同于函数的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
,我们可以确保input
和output
彼此一致,但仍然允许Worker<Any>
扩展Worker<String>
.