Дженерики в Kotlin
Дженерики позволяют писать код, параметризованный типом, и проверять этот тип на этапе компиляции. Но на JVM у обобщений есть жёсткое ограничение — стирание типов: после проверки компилятор выбрасывает аргумент типа, и в рантайме List<String> и List<Int> неотличимы. Это не деталь реализации, а причина целого класса ограничений, о которые спотыкаются на собеседовании.
Именно из-за стирания нельзя написать T::class.java для обычного параметра T — токена класса просто нет. Kotlin даёт обход: reified-параметр типа возвращает тип в рантайм, но работает только на inline-функциях. Стирание объясняет, почему обход вообще нужен, а reified — как им пользоваться. Полная карта — в слоях ниже. (Отдельное измерение — вариантность in/out, но её глубокий разбор выходит за рамки этих слоёв.)
Карта темы
- Стирание типов — почему на JVM
List<String>в рантайме превращается вListи что из этого нельзя. - Reified-параметры типа — как
inline+reifiedвозвращают реальный тип в рантайм без рефлексии.
Частые ошибки и ловушки
| Ошибка | Последствие |
|---|---|
| Считать, что аргумент типа доживает до рантайма, как в шаблонах C++ | Неверные ожидания от is T, T::class и перегрузок |
Писать T::class.java в обычной generic-функции | Не компилируется — у стёртого T нет токена класса |
Помечать параметр reified без inline у функции | Ошибка компиляции — reified возможен только при встраивании |
| Объявить две перегрузки, различающиеся лишь аргументом типа | Конфликт — после стирания у них одинаковая JVM-сигнатура |
Пробовать is List<String> | Только is List<*> — конкретный аргумент проверить нельзя |
Ждать, что reified T даст создать T() | Конструктор неизвестен — экземпляр так не создать |
Значение для собеседований
Тему спрашивают, чтобы проверить, понимаете ли вы, что обобщения на JVM — чисто компиляционный слой, стираемый перед выпуском. Кандидат, который объясняет «T::class не работает, потому что T стёрт, а reified возвращает тип только за счёт встраивания на inline», сразу опережает «ну, дженерики хранят тип».
Что обычно проверяют:
- Что такое стирание и почему в рантайме
List<String>— простоList. - Почему
T::class.javaне компилируется для обычногоT. - Как
reifiedвозвращает тип и почему он неразрывен сinline. - Почему две перегрузки, различающиеся лишь аргументом типа, конфликтуют.
Типичный неверный ответ: «JVM хранит тип дженерика в скрытом теге, надо лишь достать его рефлексией». На деле тега нет — аргумент типа удалён; единственный штатный способ иметь тип в рантайме — inline + reified либо переданный Class<T>.