В каких случаях использовать модификаторы in и out в дженериках Kotlin
Что проверяют
Понимаете ли вы вариантность, а не просто помните, что out значит «возвращает». Следом обычно спрашивают, почему List<String> можно передать как List<Any>, а MutableList<String> нельзя, и что такое проекция типа.
Как отвечать
По умолчанию дженерики в Kotlin инвариантны: Box<Cat> не является подтипом Box<Animal>, даже если Cat наследует Animal. Модификаторы это меняют:
out T(ковариантность): тип только отдаётся наружу, возвращается из функций, но не принимается параметром. ТогдаProducer<Cat>можно использовать какProducer<Animal>. Примеры из библиотек:List<out E>,Flow<out T>,StateFlow<out T>;in T(контравариантность): тип только принимается. ТогдаConsumer<Animal>подходит туда, где ждутConsumer<Cat>: кто умеет обработать любое животное, справится и с котом. Пример:Comparable<in T>;- инвариантный
T: и чтение, и запись, как вMutableList<E>.
Это вариантность на месте объявления. Есть и вариантность на месте использования, проекции типов: fun copy(from: Array<out Any>, to: Array<Any>) или MutableList<in Number>. В Java это ? extends и ? super. Звёздочная проекция List<*> означает «список неизвестного типа»: читать можно как Any?, записать ничего нельзя.
В Android-коде это встречается постоянно: функциональный тип (T) -> Unit устроен как Function1<in P1, out R>, поэтому обработчик для Any подходит туда, где ждут обработчик для String.
Компилятор проверяет позиции: в классе с out T нельзя объявить var item: T или fun set(item: T). Когда такое использование безопасно по смыслу, как contains(element: E) у List, ставят аннотацию @UnsafeVariance.
Где спотыкаются
Путают направление: говорят, что in позволяет подставить наследника. На деле in разрешает подставить обработчик более общего типа.
Считают, что out делает объект неизменяемым. Модификатор ограничивает сигнатуры, а не состояние: за List<out E> может стоять ArrayList, который меняют через другую ссылку.
Пытаются вызвать add у MutableList<out Number> и не понимают, почему параметр стал Nothing. Проекция out закрывает все методы, принимающие T.