Вариант использования сайта в Котлине

open class A
class B: A()

fun <T> copy(src: MutableList<T>, dst: MutableList<T>) {
    for (i in 0 until src.size) {
        dst.add(i, src[i])
    }
}

Я понимаю, что для вышеупомянутого кода copy function ожидает, что оба параметра типа будут точно такого же типа. С небольшой модификацией copy(src: MutableList<T>, dst: MutableList<in T>) обратите внимание на ключевое слово in, я говорю, что src должен быть точного типа T, но назначение может быть type T или любого супертипа T .

Для приведенного выше модифицированного кода я могу вызвать метод следующим образом:

fun main(args: Array<String>) {
    val l1 = mutableListOf(B(), B())
    val l2 = mutableListOf<A>()
    copy(l1, l2)
} // main

Приведенный выше copy(l1, l2) не работает, если я удаляю in из места назначения (понятно).

Мой вопрос в том, что я могу вызвать функцию без каких-либо ошибок, если обновить параметр функции src, чтобы принять out проекцию списка. например

fun <T> copy(src: MutableList<out /*notice out here*/ T>, dst: MutableList<T>) {
    for (i in 0 until src.size) {
        dst.add(i, src[i])
    }
}

В данном случае я не могу понять, что происходит под капотом. Кто-нибудь может объяснить, пожалуйста?

Обратите внимание, что это всего лишь пример из книги. Я знаю, что могу использовать List вместо неизменяемого списка в src


person mallaudin    schedule 05.11.2018    source источник


Ответы (2)


out здесь работает симметрично in:

в ключевом слове я говорю, что src должен быть именно типа T, но назначение может быть типа T или любого супертипа T

Итак, теперь вы говорите, что src должен быть MutableList типа T или любым подтипом T, а dst должен быть MutableList именно типа T.

Итак, когда у вас есть l1: MutableList<B> и l2: MutableList<A>, компилятор выводит параметр типа в copy(l1, l2) как copy<A>(l1, l2) и проверяет тип: MutableList<B> является подтипом MutableList<out A>.

Поскольку вы используете только out-совместимые операции с src и только in-совместимые операции с dst, как говорит @s1m0nw1, имеет смысл включить оба модификатора.

person Alexey Romanov    schedule 05.11.2018

поскольку вы используете функцию только одним способом, вы все равно должны использовать модификаторы дисперсии использования сайта, чтобы дать понять вызывающей стороне, что вы можете добавить к dst и получить данные от src:

fun <T> copy(src: MutableList<out T>, dst: MutableList<in T>) {
    for (i in 0 until src.size) {
        dst.add(i, src[i])
    }
}

Кроме того, поскольку src на самом деле используется как List, а не MutableList, вам следует отдавать предпочтение именно ему. В результате вам больше не понадобится модификатор out, так как List уже определил свой параметр типа T только как out:

fun <T> copy(src: List<T>, dst: MutableList<in T>)

В ответ на ваш вопрос: проблема на самом деле возникает, когда вы вызываете copy с двумя списками разного типа в вашем main, один раз с MutableList<A> и один раз с MutableList<B>. Компилятор не может сделать вывод, должен ли тип copy быть A или B. Чтобы исправить это, вам нужно дать больше информации:

1) Когда вы устанавливаете dst в MutableList<in T>, компилятор знает, что вы добавите к нему только T типы на основе src (в вашем примере это B).

2) Когда вы устанавливаете src в MutableList<out T>, компилятор понимает, что вы добавите только T и его подтипы в dst, что тоже нормально (в этом случае T будет выводиться как A).

person s1m0nw1    schedule 05.11.2018
comment
Согласен со всем в ответе @ s1m0nw1 выше. Возможно, вам будет полезно прочитать страницу официальной документации об дженериках и обратить внимание на их различие между производителями и потребителями. По сути, создается что-то, помеченное out (поэтому неизменяемый список использует out, поскольку они только производят значения, а не потребляют их); что-то с пометкой in потребляется. - person Yoni Gibbs; 05.11.2018
comment
Спасибо @s1m0nw1. in this case T will be inferred as A though понял. - person mallaudin; 05.11.2018