← Назад к вопросам

Что такое out у дженериков?

2.7 Senior🔥 111 комментариев
#Kotlin основы#Коллекции и структуры данных

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что такое модификатор out у дженериков в Kotlin/Java?

Модификатор out — это ключевой механизм для обеспечения безопасности типов в системах дженериков, реализующий принцип ковариантности (covariance). Он позволяет использовать обобщённые типы там, где ожидаются их продвинутые (более конкретные) производные типы, но с важным ограничением: тип с модификатором out может выступать только как "производитель" (producer) данных, а не как их "потребитель" (consumer).

Основная идея и проблема, которую решает out

Без модификатора out дженерики в Kotlin и Java (по умолчанию) являются инвариантными (invariant). Это означает, что Box<Cat> не является подтипом Box<Animal>, даже если Cat является подтипом Animal.

open class Animal
class Cat : Animal()

class Box<T>(private var item: T) {
    fun getItem(): T = item
    fun setItem(newItem: T) { item = newItem }
}

fun main() {
    val catBox: Box<Cat> = Box(Cat())
    // ОШИБКА компиляции! Type mismatch.
    // Box<Cat> не является подтипом Box<Animal>
    // val animalBox: Box<Animal> = catBox
}

Модификатор out делает дженерик ковариантным по типовому параметру T. Теперь Producer<Cat> является подтипом Producer<Animal>.

class Producer<out T>(private val item: T) {
    fun produce(): T = item // Разрешено: T только на выходе (return тип)
    // fun consume(newItem: T) { } // ЗАПРЕЩЕНО! T во входной позиции
}

fun main() {
    val catProducer: Producer<Cat> = Producer(Cat())
    // Теперь КОРРЕКТНО! Producer<Cat> является подтипом Producer<Animal>
    val animalProducer: Producer<Animal> = catProducer

    val producedAnimal: Animal = animalProducer.produce() // Безопасно
}

Ключевое правило: "out" означает "Только на выходе"

Модификатор out накладывает строгое ограничение: тип T может использоваться только в выходных (output) позициях:

  • Возвращаемый тип функции (fun get(): T).
  • Тип свойства, доступного только для чтения (val item: T).
  • Тип в контравариантной позиции другого дженерика (например, List<out T> внутри сигнатуры).

Запрещено использовать T во входных (input) позициях:

  • Параметры функции (fun set(item: T)).
  • Изменяемые свойства (var item: T).

Это правило гарантирует безопасность. Если бы Producer<out T> мог "потреблять" T, то через ссылку типа Producer<Animal> можно было бы попытаться передать Dog в объект, который на самом деле является Producer<Cat>, что привело бы к ClassCastException во время выполнения.

Практическое применение в Kotlin

  1. Коллекции только для чтения: Интерфейс List<out E> в Kotlin является ковариантным. Это позволяет писать гибкие и безопасные функции:

    fun printAnimals(animals: List<Animal>) {
        for (animal in animals) println(animal)
    }
    
    val cats: List<Cat> = listOf(Cat(), Cat())
    printAnimals(cats) // Безопасно, List<Cat> является подтипом List<Animal>
    
  2. Объявление проекций типов (use-site variance): Модификатор можно использовать непосредственно при объявлении переменной, не меняя объявление класса:

    class Consumer<T>(fun consume(item: T)) // Инвариантный класс
    
    val invariantConsumer: Consumer<Animal> = Consumer<Animal>()
    // val outProjection: Consumer<out Animal> = invariantConsumer // Можно присвоить
    // outProjection.consume(Cat()) // НЕЛЬЗЯ! Запрещено вызывать методы с T на входе
    

Отличие от in и сравнение с Java

  • out (ковариантность): Producer<Derived>Producer<Base>. Тип — производитель.
  • in (контравариантность): Consumer<Base>Consumer<Derived>. Тип — потребитель.
  • Без модификатора (инвариантность): Нет отношения подтипов между Box<Base> и Box<Derived>.

В Java ковариантность достигается через wildcards: <? extends Animal> (аналог out Animal), а контравариантность — через <? super Animal> (аналог in Animal). Kotlin сделал эту систему более выразительной и интегрированной в синтаксис объявления классов.

Вывод

Модификатор out — это мощный инструмент для создания типобезопасных и гибких API. Он позволяет естественным образом выражать отношения "является подтипом" для обобщённых контейнеров, строго ограничивая использование типого параметра только позициями "производства" данных, что полностью предотвращает ошибки приведения типов во время выполнения. Его понимание критически важно для эффективной работы с коллекциями, классами"> -репозиториями, Rx-потоками и другими абстракциями, построенными на дженериках.

Что такое out у дженериков? | PrepBro