Дженерики kotlin только для потребителей

Допустим, у меня есть этот пример java:

interface Sink<T> {
    void accumulate(T t);
}

public static <T> void drainToSink(Collection<T> collection, Sink<? super T> sink) {
    collection.forEach(sink::accumulate);
}

Обратите внимание, как второй параметр объявлен как ? super T. Мне это нужно, потому что я хочу вызвать этот метод следующим образом:

Sink<Object> sink = .... // some Sink implementation
Collection<String> strings = List.of("abc");
drainToSink(strings, sink);

Теперь я пытаюсь добиться того же с помощью kotlin (с которым у меня очень небольшой опыт):

interface Sink<T> {
    fun accumulate(t: T)
}

fun <T> drainToSink(collection: List<T>, sink: Sink<T>) {
   ....
}

А сейчас пытаюсь использовать:

fun main(args: Array<String>) {

     val sink = object : Sink<Any> {
         override fun accumulate(any: Any) {  }
     }

     val strings = emptyList<String>()
     drainToSink(strings, sink)
}

Интересно, что это не терпит неудачу (если я не знаю здесь слишком мало о котлине).

Я действительно ожидал, что мне нужно добавить к объявлению что-то вроде Sink<in T>, чтобы компилятор знал, что это на самом деле только или Consumer, или in T всегда включен по умолчанию?

Может ли кто-нибудь, кто знает котлин лучше меня, указать мне правильное направление?

Разве здесь T не подразумевается как Any? А поскольку у вас есть List<out T>, который также работает для первого параметра?

Jorn Vernee 05.09.2018 21:43

@JornVernee, кажется, прав - List Котлина действительно List<out E>, в отличие от его MutableList<E>.

Tomasz Linkowski 05.09.2018 21:48

@TomaszLinkowski о, черт! так что получается что-то вроде List<? extends T> ... ну звучит как ответ для меня

Eugene 05.09.2018 21:53

@JornVernee, ты прав ...

Eugene 05.09.2018 21:53
Sink, вероятно, следует просто объявить как Sink<in T> ...
Louis Wasserman 06.09.2018 00:28

@LouisWasserman, это правильный в целом, мой вопрос был в том, почему нет объявляет, что он все еще работает

Eugene 06.09.2018 10:32
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
4
6
407
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Как я уже сказал в своем комментарии, T здесь обозначается как Any. Это то, что я вижу, когда позволяю моей среде IDE добавлять явные аргументы типа к вызову drainToSink.

Поскольку List kotlin является строго производителем, поскольку он неизменяем, он объявляет свой параметр типа как out E. Вы получаете List<Any> в качестве типа параметра для drainToSink, и вполне нормально назначить ему List<String>:

val strings = emptyList<String>()
val x: List<Any> = strings // works

Если вы измените первый тип параметра на MutableList<T>, который не имеет параметра ковариантного типа, ваш пример делает завершится ошибкой:

fun <T> drainToSink(collection: MutableList<T>, sink: Sink<T>) {
    ....
}
val strings = emptyList<String>()
drainToSink(strings, sink) // type mismatch: Required: MutableList<Any>, Found: List<String>

Что ж, это интересно, поэтому в java-словах этот метод стал бы public static <T> void drainToSink(List<? extends T> collection, Sink<T> sink) { (из-за этого out), так что да, я вижу, что T будет выведен из Any.

Eugene 06.09.2018 14:38

Теперь у меня проблема с пониманием этого val strings = emptyList<String>() val x: List<Any> = strings // works или, точнее, как это переводится на java ... ну, emptyList будет List<? extends String>, но как насчет List<Any>? List<?> или List<? extends Object>, потому что насколько я понимаю это не может быть List<Object> ...

Eugene 06.09.2018 14:39

@Eugene Да, вам нужен подстановочный знак при переводе List<Any> на Java. Я предполагаю, что технически это будет List<? extends Any>, но поскольку в Java нет Any, наиболее близким из них, вероятно, является List<? extends Object>, который более или менее совпадает с List<?>. И действительно, List<?> l = (List<? extends String>) new ArrayList<String>(); тоже нормально работает.

Jorn Vernee 06.09.2018 16:03

ну, если мы "переведем" List<Any> в List<?>, как я буду переводить List<*>? :) это загадочным образом слегка съедает мой мозг

Eugene 06.09.2018 16:13

@Eugene Также для List<?> я бы сказал.

Jorn Vernee 06.09.2018 16:26

Другие вопросы по теме