Допустим, у меня есть этот пример 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 всегда включен по умолчанию?
Может ли кто-нибудь, кто знает котлин лучше меня, указать мне правильное направление?
@JornVernee, кажется, прав - List Котлина действительно List<out E>, в отличие от его MutableList<E>.
@TomaszLinkowski о, черт! так что получается что-то вроде List<? extends T> ... ну звучит как ответ для меня
@JornVernee, ты прав ...
Sink, вероятно, следует просто объявить как Sink<in T> ...
@LouisWasserman, это правильный в целом, мой вопрос был в том, почему нет объявляет, что он все еще работает




Как я уже сказал в своем комментарии, 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.
Теперь у меня проблема с пониманием этого val strings = emptyList<String>() val x: List<Any> = strings // works или, точнее, как это переводится на java ... ну, emptyList будет List<? extends String>, но как насчет List<Any>? List<?> или List<? extends Object>, потому что насколько я понимаю это не может быть List<Object> ...
@Eugene Да, вам нужен подстановочный знак при переводе List<Any> на Java. Я предполагаю, что технически это будет List<? extends Any>, но поскольку в Java нет Any, наиболее близким из них, вероятно, является List<? extends Object>, который более или менее совпадает с List<?>. И действительно, List<?> l = (List<? extends String>) new ArrayList<String>(); тоже нормально работает.
ну, если мы "переведем" List<Any> в List<?>, как я буду переводить List<*>? :) это загадочным образом слегка съедает мой мозг
@Eugene Также для List<?> я бы сказал.
Разве здесь
Tне подразумевается какAny? А поскольку у вас естьList<out T>, который также работает для первого параметра?