ПрограммированиеBackend разработчик

Как в Kotlin реализованы generics? Какие ограничения существуют, как работает инвариантность, ковариантность и контравариантность, и чем они отличаются от дженериков в Java? Приведите примеры использования.

Проходите собеседования с ИИ помощником Hintsage

Ответ

Generics в Kotlin позволяют создавать универсальные и типобезопасные структуры данных и функции. Основная особенность: Kotlin реализует систему generic типов на уровне компиляции, как и Java, но с более строгой типизацией и расширенным синтаксисом variance (ковариантность и контравариантность).

Ограничения дженериков:

  • Тип-параметры по умолчанию инвариантны.
  • Нельзя создать экземпляр типа-параметра (T() запрещено).
  • Нет доступа к информации о типах generic во время исполнения (type erasure).

Variance:

  • Ковариантность (out T): позволяет использовать подтипы.
  • Контравариантность (in T): позволяет использовать супертипы.
  • Инвариантность: тип без variance-модификатора.

Пример ковариантности:

interface Producer<out T> { fun produce(): T }

Пример контравариантности:

interface Consumer<in T> { fun consume(item: T) }

Отличие от Java:

  • Синтаксис более явный и лаконичный (out вместо ? extends, in вместо ? super).
  • Нет wildcard-типов (?, только in/out).
  • Запрет на создание экземпляра generic-параметра.

Вопрос с подвохом

Вопрос: "Можно ли в Kotlin объявить массив масивов (Array<Array<Int>>) как Array<out Array<Int>> и что произойдет при попытке записи в такой массив?"

Ответ: Да, можно объявить как Array<out Array<Int>>, но такой массив становится только для чтения (read-only):

val arr: Array<out Array<Int>> = Array(1) { Array(1) { 0 } } arr[0] = arrayOf(1, 2, 3) // Ошибка компиляции!

Попытка записать значение приведёт к ошибке — generic array с out-параметром не позволяет записывать элементы, потому что тогда нарушилась бы типобезопасность.

Примеры реальных ошибок из-за незнания тонкостей темы


История

В команде пытались создать массив generic-объектов с типом out-параметра, а после — положить в него значения через set(index, value). Код компилировался, но вызывал ошибку в рантайме, и несколько функций оказались неработоспособны.


История

Однажды при миграции библиотеки с Java на Kotlin оставили wildcard-типы (? extends ...), а в Kotlin просто скопировали типы без изменения на out/in. Результат — компиляция не прошла, а при "обходе" ошибка пошла в рантайме, усложнив процесс дебага.


История

Использовали in/out variance с пользовательским классом, но перепутали модификаторы, объявив interface Stack<in T> вместо Stack<out T>. Это привело к невозможности возвращать элементы из стека: сигнатура метода нарушала system out/in contract.