Как получить класс дженерик-параметра в Kotlin? (reified, inline и TypeReference)
Решение с помощью reified и inline-функций
В Kotlin для получения класса дженерик-параметра на этапе выполнения используется ключевое слово reified в сочетании с inline. Это позволяет "материализовать" тип, который в Java стирается. Без reified информация о T недоступна во время выполнения из-за стирания типов (type erasure).
Базовый пример
Объявите функцию как inline и добавьте reified перед параметром типа:
inline fun <reified T> processList(items: List<T>) {
println("Processing items of type: ${T::class.simpleName}")
}
// Использование:
processList(listOf("a", "b")) // Выведет: Processing items of type: String
processList(listOf(1, 2, 3)) // Выведет: Processing items of type: Int
Теперь T::class работает, потому что на месте вызова компилятор подставляет реальный тип (благодаря инлайнингу).
Получение полного дженерик-типа (например, List<String>)
Простой T::class даст только сырой класс (например, List::class), но не параметр. Чтобы получить полный тип с аргументами, используйте typeOf<T>() (доступно с Kotlin 1.3.40+):
import kotlin.reflect.typeOf
inline fun <reified T> printFullType(value: T) {
val type = typeOf<T>()
println("Full type: $type")
}
// Пример:
printFullType(listOf("hello")) // Full type: kotlin.collections.List<kotlin.String>
printFullType(mapOf(1 to "a")) // Full type: kotlin.collections.Map<kotlin.Int, kotlin.String>
Когда reified не подходит (например, в классах или интерфейсах)
reified работает только в inline-функциях и не может быть использован в обычных классах, свойствах или абстрактных методах. В таких случаях применяйте паттерн TypeReference или передавайте Class явно:
abstract class TypeReference<T> {
val type: java.lang.reflect.Type
get() = (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0]
}
// Использование:
class MyProcessor : TypeReference<List<String>>() {
fun process() {
println("Type: $type") // Выведет: java.util.List<java.lang.String>
}
}
Работа с дженериками в рантайме через Gson/Moshi
Библиотеки сериализации часто используют этот паттерн. Например, в Gson:
import com.google.gson.reflect.TypeToken
val type = object : TypeToken<List<String>>() {}.type
val list: List<String> = Gson().fromJson(jsonString, type)
Важные замечания
- Производительность:
inlineувеличивает размер байткода, но ускоряет выполнение. Используйте только для небольших функций. - Ограничения:
reifiedнельзя использовать в функциях, которые не являютсяinline, или внутри лямбд, не помеченных какinline. - Альтернатива для Java-совместимости: Если вы пишете API для Java, лучше передавать
Class<T>явно.
Таким образом, для большинства сценариев используйте inline fun <reified T> — это идиоматичный способ получить класс дженерик-параметра в Kotlin. Если нужно сохранить полную информацию о вложенных дженериках, применяйте typeOf<T>() или TypeReference.
Альтернативный подход: передача Class параметра вручную
Если вы не можете использовать reified (например, функция не может быть inline из-за архитектурных ограничений), вернитесь к классическому Java-подходу — передавайте Class<T> как параметр:
fun <T> processList(items: List<T>, clazz: Class<T>) {
println("Type: ${clazz.simpleName}")
}
// Использование:
processList(listOf("a"), String::class.java)
processList(listOf(1), Int::class.java)
Этот метод совместим с Java-кодом и не требует инлайнинга. Недостаток — загромождает сигнатуру функции и требует от вызывающего кода явно указывать тип.
Использование Any? и is-проверок
Если вам нужно просто проверить тип элементов в коллекции (а не получить сам параметр), можно обойтись без дженериков:
fun processItems(items: List<*>) {
items.firstOrNull()?.let { first ->
when (first) {
is String -> println("List of Strings")
is Int -> println("List of Ints")
else -> println("Unknown type")
}
}
}
Это не даст вам класс T, но решит задачу проверки типа во время выполнения. Однако помните, что пустой список (listOf()) не позволит определить тип.