Как получить аргументы фактического типа для параметра reified generic в Котлин?

Используя параметры типа reified, можно написать встроенную функцию, которая работает с параметром типа через отражение во время выполнения:

inline fun <reified T: Any> f() {
    val clazz = T::class
    // ...
}

Но когда f вызывается с параметром, который сам по себе является общим классом, похоже, нет способа получить его фактические аргументы типа через T::class:

f<List<Integer>>() // T::class is just kotlin.collections.List

Есть ли способ получить фактические аргументы типа овеществленного общего с помощью отражения?

Ответы

Ответ 1

Из-за стирания типа фактические общие аргументы не могут быть получены с помощью токена T::class универсального класса. Разные объекты класса должны иметь один и тот же токен класса, поэтому он не может содержать фактические общие аргументы.

Но есть технология, называемая токены супертипа, которая может давать фактические аргументы типа в случае, когда тип известен во время компиляции (это верно для обобщенных обобщений в Kotlin из-за встраивания).


Редактировать: Начиная с Kotlin 1.3.50, в соответствии с методикой, описанной ниже, для получения информации о типе для параметра типа reified больше нет необходимости. Вместо этого вы можете использовать typeOf<T>() для параметров типа reified.


Хитрость заключается в том, что компилятор сохраняет фактические аргументы типа для неуниверсального класса, производного от универсального класса (все его экземпляры будут иметь одинаковые аргументы, хорошее объяснение здесь). Они доступны через clazz.genericSuperClass.actualTypeArguments экземпляра Class<*>.

Учитывая все это, вы можете написать класс утилит следующим образом:

abstract class TypeReference<T> : Comparable<TypeReference<T>> {
    val type: Type = 
        (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0]

    override fun compareTo(other: TypeReference<T>) = 0
}

Explained in Jackson TypeReference which uses the same approach. Jackson Kotlin module uses it on reified generics.

После этого в встроенной функции с улучшенным обобщенным значением TypeReference необходимо разделить на подклассы (будет использовано объектное выражение), а затем можно использовать его type.

Пример:

inline fun <reified T: Any> printGenerics() {
    val type = object : TypeReference<T>() {}.type
    if (type is ParameterizedType)
        type.actualTypeArguments.forEach { println(it.typeName) }
}

printGenerics<HashMap<Int, List<String>>>():

java.lang.Integer
java.util.List<? extends java.lang.String>

Ответ 2

Если вы используете библиотеку gson уже в проекте (например, разбор json). Это обеспечивает класс для этого. com.google.gson.reflect.TypeToken(). Так что вы можете использовать что-то вроде

 f(object : TypeToken<List<Integer>>() {}.type)