0Pricing
Kotlin Academy · Урок

Вариативность на месте объявления

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 — локальная установка не требуется.

Все уроки этого курса

  1. Обобщённые функции и классы
  2. Вариативность на месте объявления
  3. Вариативность на месте использования
  4. Ограничения обобщений
← Назад к Kotlin Academy