На этом шаге мы рассмотрим назначение и использование этого ключевого слова.
Иногда полезно знать конкретный тип обобщенного параметра. Сделать это можно с помощью ключевого слова reified.
Представьте, что нужно извлечь награду из списка возможных наград и, в зависимости от типа случайно выпавшей награды, вернуть зарезервированную награду желаемого типа или ту, что была выбрана из списка. Функция randomOrBackupLoot() пытается выразить эту логику:
fun <T> randomOrBackupLoot(backupLoot: () -> T): T { val items = listOf(Coin(14), Fedora("a fedora of the ages", 150)) val randomLoot: Loot = items.shuffled().first() return if (randomLoot is T) { randomLoot } else { backupLoot() } } fun main(args: Array<String>) { randomOrBackupLoot { Fedora("a backup fedora", 15) }.run { // Выведет backup fedora или fedora of the ages println(name) } }
Рис.1. Функция randomOrBackupLoot()
Если вы введете этот код, то обнаружите, что он не работает. IntelliJ выделит параметр типа Т как ошибку (рисунок 2).
Рис.2. Нельзя проверить экземпляр стертого типа
Kotlin обычно запрещает проверку типа Т, потому что обобщенные типы подвержены эффекту стирания типов - это значит, что информация о типе Т недоступна во время выполнения. В Java тоже есть такое правило.
Если посмотреть на байт-код функции randomOrBackupLoot(), то можно увидеть эффект стирания типов в выражении randomLoot is T:
return (randomLoot != null ? randomLoot instanceof Object : true) ? randomLoot : backupLoot.invoke();
Как видите, там, где вы раньше использовали Т, появился Object, потому что компилятор больше не узнает тип Т во время выполнения программы. Вот почему проверка типа для объявленного обобщения обычным путем невозможна.
Но, в отличие от Java, Kotlin предоставляет ключевое слово reified, которое позволяет сохранять информацию о типе во время выполнения программы.
Ключевое слово reified используется во встраиваемых функциях следующим образом:
inline fun <reified T> randomOrBackupLoot(backupLoot: () -> T): T { val items = listOf(Coin(14), Fedora("a fedora of the ages", 150)) val randomLoot: Loot = items.shuffled().first() return if (randomLoot is T) { randomLoot } else { backupLoot() } } fun main(args: Array<String>) { randomOrBackupLoot { Fedora("a backup fedora", 15) }.run { // Выведет backup fedora или fedora of the ages println(name) } }
Рис.3. Функция randomOrBackupLoot() с ключевым словом reified
Теперь проверка типа first is для T возможна, потому что информация о типе сохранена. Информация об обобщенном типе, которая обычно стирается, теперь сохраняется, чтобы компилятор мог проверить тип обобщенного параметра.
Байт-код обновленной randomOrBackupLoot() показывает, что для Т сохраняется актуальный тип вместо Object:
randomLoot$iv instanceof Fedora ? randomLoot$iv : new Fedora("a backup fedora", 15);
Использование ключевого слова reified позволяет исследовать тип обобщенного параметра без применения рефлексии (определение имени или типа свойства или функции во время выполнения программы обычно является ресурсоемкой операцией).
Со следующего шага мы начнем рассматривать расширения.