如何在 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&lt;T : ViewModel&gt; : Fragment() 类和 ViewModelProviders .of(somethingNotInteresting) .get(getViewModelClass()) 内的方法,从您的代码 inline fun &lt;reified R: T&gt; getViewModelClass(): Class&lt;R&gt; = R::class.java 中提供 T.class:java,但编译器抱怨“不能使用 'T' 作为精化类型。改用类”。你知道怎么解决吗? private inline fun className() = T::class.java private fun initFragmentCommunication() fragmentEventViewModel = ViewModelProviders.of(this, viewModelFactory).get(className>()) retrieveSomething 无法从 java 中调用 @anvarik 正确,我并没有将我的答案局限于可以从 Java 调用的要求,因为它是 Kotlin 唯一的问题。【参考方案2】:

您需要的是通用参数的 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 中获取泛型参数类

随机森林分类如何在幕后工作?

Kotlin泛型 ② ( 可变参数 vararg 关键字与泛型结合使用 | 使用 [] 运算符获取指定可变参数对象 )

外键和引用类如何在 Hibernate 实体中一起存在?

serversocket 类如何在同一个端口上服务多个客户端连接?

如何在Kotlin中指定递归泛型参数?