Вариативность на месте объявления
in и out
«Вариативность на месте объявления» — бесплатный урок Kotlin Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Kotlin Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Kotlin Academy содержит 4 уроков всего.
Проблема вариантности
Является ли List<String> разновидностью List<Any>? Иногда да, иногда нет. Вариантность описывает взаимосвязь обобщённых типов, когда связаны их параметры типа. В Kotlin для этого используются out и in.
Инвариантность по умолчанию
По умолчанию обобщённые типы инвариантны: Box<String> — NOT Box<Any>, даже если String является подтипом любого типа. Это предотвращает небезопасные операции.
class Box<T>(val value: T)
fun main() {
val strBox = Box("hi")
// val anyBox: Box<Any> = strBox // would not compile
println(strBox.value)
}Ковариантность с out
Если пометить параметр типа как out, он становится ковариантным: Producer<String> становится подтипом Producer<Any>. Такой тип может использоваться только в позициях вывода, например в возвращаемых типах.
class Producer<out T>(private val item: T) {
fun produce(): T = item
}
fun main() {
val strProducer: Producer<String> = Producer("hello")
val anyProducer: Producer<Any> = strProducer
println(anyProducer.produce())
}Почему out безопасен
Если класс только производит, то есть возвращает, T и никогда его не принимает, считать Producer<String> типом Producer<Any> безопасно, поскольку каждый возвращаемый им String также относится к универсальному типу.
out в стандартной библиотеке
Доступный только для чтения List<out E> в Kotlin является ковариантным, поэтому List<String> можно использовать там, где ожидается List<Any>.
fun printAll(items: List<Any>) {
items.forEach { println(it) }
}
fun main() {
val names: List<String> = listOf("Ann", "Bob")
printAll(names)
}Контравариантность с in
Если пометить параметр типа как in, он становится контравариантным: Consumer<Any> становится подтипом Consumer<String>. Такой тип может использоваться только в позициях ввода, например в параметрах.
class Consumer<in T> {
fun consume(item: T) {
println("Consumed $item")
}
}
fun main() {
val anyConsumer: Consumer<Any> = Consumer()
val strConsumer: Consumer<String> = anyConsumer
strConsumer.consume("hi")
}Почему in безопасен
Потребитель, принимающий значение любого типа, конечно, может принять и String. Поэтому Consumer<Any> может использоваться везде, где требуется Consumer<String>.
Мнемоника PECS
Запомните правило Producer расширяет, Consumer является надтипом, или, в терминах Kotlin: out используется для производителей, то есть для вывода, а in — для потребителей, то есть для ввода. Тип, который только выдаёт T, использует out; тип, который только принимает T, использует in.
Сочетание in и out
Параметр типа, используемый и для ввода, и для вывода, как в изменяемом списке, должен оставаться инвариантным. Типы Function демонстрируют оба варианта: параметры являются in, а возвращаемое значение — out.
fun main() {
val f: (Number) -> Int = { it.toInt() }
val g: (Int) -> Number = f
println(g(3.0.let { Number::class; 5 }))
}Объявление вариантности в одном месте
Вариантность, объявленная в месте определения, задаётся один раз — непосредственно в классе с помощью in/out. После этого вариантность автоматически применяется при каждом использовании класса, в отличие от Java, где подстановочные типы приходится повторять при каждом использовании.
Выбор in или out
Спросите себя: этот класс только производит T — тогда используйте out, только принимает T — используйте in, или делает и то и другое — оставьте его инвариантным? Соответствие модификатора роли делает обобщённые типы гибкими и безопасными.
Быстрая проверка
Что показывает модификатор out?
Итоги
По умолчанию обобщённые типы инвариантны. Используйте out для ковариантности, когда тип только производит значения, и in для контравариантности, когда тип только принимает значения. Помните: производители используют out, а потребители — in. Далее Вы примените вариантность в месте использования с помощью проекций типов.
Часто задаваемые вопросы
Урок «Вариативность на месте объявления» бесплатный?
Да — полный текст урока «Вариативность на месте объявления» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Kotlin Academy, подпишись на CoddyKit PRO. Курс Kotlin Academy содержит 4 уроков всего.
Чему я научусь в уроке «Вариативность на месте объявления»?
in и out Ты практикуешь Kotlin Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Kotlin Academy?
Предыдущий опыт не требуется. Kotlin Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.
Сколько времени занимает урок «Вариативность на месте объявления»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Kotlin Academy?
Да. Каждый урок Kotlin Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Обобщённые функции и классы
- Вариативность на месте объявления
- Вариативность на месте использования
- Ограничения обобщений