【问题标题】:How to get generic parameter class in Kotlin如何在 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# 中那样对泛型参数提供类约束,或者我可以使用其他一些语法来获取泛型参数的类型信息?

【问题讨论】:

  • 您能否为您的问题提供更多上下文,因为具有类型参数的函数与具有类型参数的类将有两个不同的答案。您接受了涵盖其中一种情况的仅链接答案。
  • 您的问题应该显示导致错误的代码的完整上下文。在某些情况下,您的代码是正确的(如果我们想象它在具有具体类型的内联函数中),在其他情况下则不然。泛型可能有两种情况,但你没有说清楚。

标签: generics kotlin


【解决方案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】:
  1. 使用类比较。见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. 使用isAssignableFrom。见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")
         }
     }
    

对于 SharedPreferences 中的 Double,请参阅 https://stackoverflow.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

      【讨论】:

        猜你喜欢
        • 2019-02-26
        • 2018-07-26
        • 2016-07-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多