Как получить общий класс параметров в Котлине

Firebase snapshot.getValue() ожидает вызова следующим образом:

snapshot?.getValue(Person::class.java)

Однако я хотел бы заменить Person универсальным параметром, который передается в класс через объявление класса, т.е.

class DataQuery<T : IModel> 

и используйте этот универсальный параметр, чтобы сделать что-то вроде этого:

snapshot?.getValue(T::class.java)

но когда я пытаюсь это сделать, я получаю сообщение о том, что

только классы могут использоваться в левой части литерала класса

Можно ли обеспечить ограничение класса для универсального параметра, как в С#, или есть какой-то другой синтаксис, который я могу использовать, чтобы получить информацию о типе для универсального параметра?

Ответ 1

Для класса с общим параметром T вы не можете сделать этого, потому что у вас нет информации о типе для T, так как JVM стирает информацию о типе. Поэтому такой код не может работать:

class Storage<T: Any> {
    val snapshot: Snapshot? = ...

    fun retrieveSomething(): T? {
        return snapshot?.getValue(T::class.java) // ERROR "only classes can be used..."
    }
}

Но вы можете сделать эту работу, если тип T подтвержден и используется внутри встроенной функции:

class Storage {
    val snapshot: Snapshot? = ...

    inline fun <reified T: Any> retrieveSomething(): T? {
        return snapshot?.getValue(T::class.java)
    }
}

Обратите внимание, что встроенная функция, если public может обращаться только к открытым членам класса. Но вы можете иметь два варианта функции: тот, который получает параметр класса, который не является встроенным, и обращается к частным внутренним элементам, а также к другой встроенной вспомогательной функции, которая выполняет переопределение из параметра inferred type:

class Storage {
    private val snapshot: Snapshot? = ...

    fun <T: Any> retrieveSomething(ofClass: Class<T>): T? {
        return snapshot?.getValue(ofClass)
    }

    inline fun <reified T: Any> retrieveSomething(): T? {
        return retrieveSomething(T::class.java)
    }
}

Вы также можете использовать KClass вместо Class, чтобы вызывающие абоненты, которые являются только Kotlin, могут просто использовать MyClass::class вместо MyClass::class.java

Если вы хотите, чтобы класс взаимодействовал с встроенным методом в generics (это означает, что класс Storage хранит только объекты типа T):

class Storage <T: Any> {
    val snapshot: Snapshot? = ...

    inline fun <reified R: T> retrieveSomething(): R? {
        return snapshot?.getValue(R::class.java)
    }
}

Ссылка на типы reified в встроенных функциях: https://kotlinlang.org/docs/reference/inline-functions.html#reified-type-parameters

Ответ 2

Что вам нужно, это модификатор reify для вашего общего параметра, вы можете прочитать об этом здесь. https://kotlinlang.org/docs/reference/inline-functions.html#reified-type-parameters Поэтому, если вы сделаете что-то подобное:

inline fun <reified T : Any>T.logTag() = T::class.java.simpleName

вы получите имя фактического класса вызывающего, а не "Объект".

Ответ 3

вы можете получить его тип класса, как это

snapshot?.getValue((this.javaClass
                        .genericSuperclass as ParameterizedType)
                        .actualTypeArguments[0] as Class<T>)

Ответ 4

См. Https://dev.to/cjbrooks12/kotlin-reified-generics-explained-3mie.

inline fun <reified T : Number> SharedPreferences.getData(key: String): T? {
    val cls = T::class.java
    return if (cls.isAssignableFrom(Integer::class.java)) {
        getInt(key, 0) as T
    } else if (cls.isAssignableFrom(Long::class.java)) {
        getLong(key, 0) as T
    } else {
        throw IllegalStateException("Unsupported type")
    }
}