如何在 Kotlin 获得泛型参数类

Firebase 的 snapshot.getValue()预计将被命名为:

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

但是,我想用一个通用参数替换 Person,该参数通过类声明即传递给类。

class DataQuery<T : IModel>

并使用这个通用参数执行以下操作:

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

但是当我尝试这样做的时候,我会得到一个错误

只有类可以在类文字的左边使用

是否可以像 C # 那样为泛型参数提供类约束,或者是否有其他语法可以用来获得泛型参数的类型信息?

91857 次浏览

What you need is reified modifier for your generic param, you can read about it here. https://kotlinlang.org/docs/reference/inline-functions.html#reified-type-parameters So if you do something like that:

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

you will get name of the actual caller class, not "Object".

For a class with generic parameter T, you cannot do this because you have no type information for T since the JVM erases the type information. Therefore code like this cannot work:

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


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

But, you can make this work if the type of T is reified and used within an inline function:

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


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

Note that the inline function if public can only access public members of the class. But you can have two variants of the function, one that receives a class parameter which is not inline and accesses private internals, and another inline helper function that does the reification from the inferred type parameter:

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)
}
}

You can also use KClass instead of Class so that callers that are Kotlin-only can just use MyClass::class instead of MyClass::class.java

If you want the class to cooperate with the inline method on the generics (meaning that class Storage only stores objects of type T):

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


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

The link to reified types in inline functions: https://kotlinlang.org/docs/reference/inline-functions.html#reified-type-parameters

you can get its class type like this

snapshot?.getValue((this.javaClass
.genericSuperclass as ParameterizedType)
.actualTypeArguments[0] as Class<T>)
  1. Using class comparison. See https://stackoverflow.com/a/61756761/2914140.

     inline fun <reified T> SharedPreferences.getData(key: String, defValue: Any? = null): T? =
    when (T::class) {
    Integer::class -> {
    val value = getInt(key, (defValue as? Int) ?: 0) as T
    if (value == 0) defValue as? T else value
    }
    Long::class -> {
    val value = getLong(key, (defValue as? Long) ?: 0L) as T
    if (value == 0) defValue as? T else value
    }
    Boolean::class -> {
    val value = getBoolean(key, (defValue as? Boolean) ?: false) as T
    if (value == false) defValue as? T else value
    }
    String::class -> getString(key, defValue as? String) as T?
    else -> throw IllegalStateException("Unsupported type")
    }
    
  2. Using isAssignableFrom. See 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")
    }
    }
    

For Double in SharedPreferences see https://stackoverflow.com/a/45412036/2914140.

Use:

val s: String? = preferences.getData("key", "")
val i = preferences.getData<Int>("key")

Using typeOf is another option. (Added in Kotlin 1.3)

Example:

@OptIn(ExperimentalStdlibApi::class)
inline fun <reified T> someMethod(data: T) {


val typeClassifier = typeOf<T>().classifier


if (typeClassifier == List::class) {
//Do something
}


}

This is what I have.

class ClubsViewModel<T>(clazz: Class<T>) :  BaseViewModel<T>(clazz) {
private var _mClubs = MutableLiveData<List<T>>()


listenToFireStoreCollection("Clubs", _mClubs)
...
}


class BViewModel<T>(clazz: Class<T>) :  BaseViewModel<T>(clazz) {
  

private var _mBs = MutableLiveData<List<T>>()
listenToFireStoreCollection("Bname", _mBs)
...
}


class BaseViewModel<T>(val clazz: Class<T>) {
protected val mFirestore = Firebase.firestore


protected  fun listenToFireStoreCollection(val collectionName: String, liveData: MutableLiveData<List<T>>)
mFirestore.collection(collectionName).addSnapshotListener { snapshot, e ->
if (e != null) {
return@addSnapshotListener
}
if (snapshot != null) {
liveData.value = snapshot.documents.mapNotNull { it.toObject(clazz) }
}
}
}
}
//FRAGMENT EXAMPLES.
class ClubsFragment : Fragment() {


private val mClubsViewModel: ClubsViewModel<ClubsFSEntity> by viewModels()
...
}


class BsFragment : Fragment() {


private val mBsViewModel: BsViewModel<BsFSEntity> by viewModels()
...
}


I have a similar problem here: Hilt Dependency injection with Class<T> in ViewModel