Почему параметры контравариантного типа в параметрах функции рассматриваются в положении «вне»? - PullRequest
2 голосов
/ 03 мая 2019

Мне сложно описать по-английски, но вот проблема:

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>. Может кто-нибудь объяснить это?

Ответы [ 2 ]

2 голосов
/ 04 мая 2019

Позиция аргумента называется контравариантной, поскольку ее дисперсия идет в противоположном направлении относительно дисперсии класса.Это означает, что супертипы класса могут принимать подтипы типа аргумента в качестве параметра и наоборот.

Давайте рассмотрим некоторый фактический тип параметра S.В этом примере тип Accepter<S>, который является супертипом Accepter<Any>, должен принимать подтип из Consumer<Any> в качестве параметра, но с данной сигнатурой он принимает Consumer<S>, то есть не 't подтип 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 голос
/ 04 мая 2019

Параметры функции, которые сами позволяют вводить, логически эквивалентны возвращаемым значениям для функции, которые, очевидно, находятся в положении «вне».

Рассмотрим этот простой пример:

interface Worker<in T> {
    fun work(output: Consumer<T>)
}

Этологически эквивалентно

interface Worker<in T> {
    fun work(): T
}

work() can output значение в любом случае.

Пример этого сбоя:

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> расширять Worker<String>.

...