如何在 Kotlin 中获取泛型参数类
Posted
技术标签:
【中文标题】如何在 Kotlin 中获取泛型参数类【英文标题】:How to get generic parameter class in Kotlin 【发布时间】:2015-12-06 20:40:04 【问题描述】:Firebase 的 snapshot.getValue()
预计调用如下:
snapshot?.getValue(Person::class.java)
但是我想用通过类声明传递给类的通用参数替换Person
,即
class DataQuery<T : IModel>
并使用该通用参数执行以下操作:
snapshot?.getValue(T::class.java)
但是当我尝试这样做时,我收到一条错误消息,指出
只有类可以用在类文字的左侧
是否可以像在 C# 中那样对泛型参数提供类约束,或者我可以使用其他一些语法来获取泛型参数的类型信息?
【问题讨论】:
您能否为您的问题提供更多上下文,因为具有类型参数的函数与具有类型参数的类将有两个不同的答案。您接受了涵盖其中一种情况的仅链接答案。 您的问题应该显示导致错误的代码的完整上下文。在某些情况下,您的代码是正确的(如果我们想象它在具有具体类型的内联函数中),在其他情况下则不然。泛型可能有两种情况,但你没有说清楚。 【参考方案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)
注意内联函数if public 只能访问类的公共成员。但是您可以有该函数的两种变体,一种接收非内联并访问私有内部的类参数,另一种内联辅助函数从推断的类型参数进行具体化:
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
如果您希望该类与泛型上的内联方法配合(即类Storage
只存储T
类型的对象):
class Storage <T: Any>
val snapshot: Snapshot? = ...
inline fun <reified R: T> retrieveSomething(): R?
return snapshot?.getValue(R::class.java)
内联函数中具体类型的链接:https://kotlinlang.org/docs/reference/inline-functions.html#reified-type-parameters
【讨论】:
我有abstract class ViewModelFragment<T : ViewModel> : Fragment()
类和 ViewModelProviders .of(somethingNotInteresting) .get(getViewModelClass())
内的方法,从您的代码 inline fun <reified R: T> getViewModelClass(): Class<R> = R::class.java
中提供 T.class:java,但编译器抱怨“不能使用 'T' 作为精化类型。改用类”。你知道怎么解决吗?
private inline fun 您需要的是通用参数的 reified 修饰符,您可以在此处阅读。 https://kotlinlang.org/docs/reference/inline-functions.html#reified-type-parameters 所以如果你这样做:
inline fun <reified T : Any>T.logTag() = T::class.java.simpleName
您将获得实际调用者类的名称,而不是“对象”。
【讨论】:
别忘了把你的类型参数声明为T : Any
,否则会为空。
另外,不要忘记函数必须内联声明。
那实际上是行不通的。您将收到非通用参数类。【参考方案3】:
你可以像这样得到它的类类型
snapshot?.getValue((this.javaClass
.genericSuperclass as ParameterizedType)
.actualTypeArguments[0] as Class<T>)
【讨论】:
出现错误:java.lang.ClassCastException:java.lang.Class 无法转换为 java.lang.reflect.ParameterizedType @SachidanandaSahu 可能您正在尝试将类本身强制转换(this.javaClass 为 ParameterizedType),或者您可能有一些继承【参考方案4】:使用类比较。见https://***.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")
使用isAssignableFrom
。见https://dev.to/cj***s12/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")
对于 SharedPreferences 中的 Double
,请参阅 https://***.com/a/45412036/2914140。
用途:
val s: String? = preferences.getData("key", "")
val i = preferences.getData<Int>("key")
【讨论】:
【参考方案5】:使用typeOf 是另一种选择。 (在 Kotlin 1.3 中添加)
例子:
@OptIn(ExperimentalStdlibApi::class)
inline fun <reified T> someMethod(data: T)
val typeClassifier = typeOf<T>().classifier
if (typeClassifier == List::class)
//Do something
【讨论】:
【参考方案6】:这就是我所拥有的。
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()
...
我这里也有类似的问题:Hilt Dependency injection with Class<T> in ViewModel
【讨论】:
以上是关于如何在 Kotlin 中获取泛型参数类的主要内容,如果未能解决你的问题,请参考以下文章
Kotlin泛型 ② ( 可变参数 vararg 关键字与泛型结合使用 | 使用 [] 运算符获取指定可变参数对象 )