【问题标题】:Generics in Kotlin: How to instantiate generic class which implements an InterfaceKotlin 中的泛型:如何实例化实现接口的泛型类
【发布时间】:2021-09-11 20:56:36
【问题描述】:

我的问题是当泛型类实现接口时我无法实例化它。

实例化代码如下;

class MainClass {
    fun mainMethod() {
        val access = EADBAccess<AppUserModel>(AppUserModel::class.java)
    }
}

在这个主类中我产生了一个错误。 错误是

其他相应的类如下。

EADBModelI 接口

interface EADBModelI {
    var id: String
}

AppUserModel 类

class AppUserModel : EADBModelI {
    override var id: String
        get() = id
        set(value) { id = value }
    
    var name: String
        get() = name
        set(value) { name = value}
    
}

EADBAccess 类

Class EADBAccess<in T : EADBModelI>(private val typeParameterClass: Class<T>) {
    fun getSingleDocument(source: Source = Source.DEFAULT, docRef: DocumentReference, handler: ResultHandlerI<T>) {

        docRef.get(source).addOnCompleteListener { taskResult ->
            if (taskResult.isSuccessful) {
                val snapshot = taskResult.result
                if (snapshot!!.exists()) {
                    val model : T = snapshot.toObject(typeParameterClass)
                    model!!.id = snapshot.reference.id
                    handler.onSuccess(model)
                }
            } else {
                handler.onFailure(taskResult.exception)
            }
        }
    }
}

ResultHandlerI 接口

interface ResultHandlerI<T> {
    fun onSuccess(data: T)
    fun onFailure(e: Exception)
}

【问题讨论】:

  • 我相信这段代码应该可以正常工作。重新检查一切,这似乎是一个错误。也许有两个不同的 EADBModelIAppUserModel 定义或类似的东西。
  • @broot 我将 EADBModelI 从接口更改为抽象类并且它起作用了 ;)
  • 其他:您可能已经简化了此示例的代码,但 AppUserModel 中的 getter 和 setter 应该使用 the field identifier,而不是 idname

标签: android firebase kotlin generics


【解决方案1】:

我复制了您的代码并使其可执行(请参阅此答案底部的“可运行代码”)。当我运行它时,我得到了一个错误:

Type parameter T is declared as 'in' but occurs in 'invariant' 
position in type ResultHandlerI<T>

错误位置

这发生在哪里?首先,类型参数T 定义在类EADBAccess 中。 T 被标记为in

class EADBAccess<in T : EADBModelI>

T也用在fun getSingleDocument的参数handler中时会发生错误:

  fun getSingleDocument(source: String, docRef: String, handler: ResultHandlerI<T>) {
    // ...
  }

tl;博士

快速解决方法是删除in

class EADBAccess<T : EADBModelI>

现在,当我运行代码时,它会编译、运行和打印:

success: AppUserModel(id='docRef', name='source')

说明

Kotlin 文档 Generics: in, out, where 详细介绍。

[...] Kotlin 提供了一个 [...] 方差注释:in。它使类型参数逆变,这意味着它只能被消费而不能被生产。

Array&lt;in String&gt;对应Java的Array&lt;? super String&gt;

所以如果使用&lt;in T : EADBModelI&gt;,那么T 将是EADBModelI 接口的一些未知实现。但这还不够清楚 - ResultHandlerI 需要知道 不变量 T,而不是变量范围。

虽然一方面T 是一个输入(因此in T 是有意义的),但实际上T 也是一个输出,因为它被用于定义类型ResultHandlerI.

定义&lt;T : EADBModelI&gt; 使T 不变-在运行时它将是EADBModelI 的单个特定实现(在您的示例中为AppUserModel)。 T 的这种实现既可以用作输入,也可以用作输出。

更多解释请见this answer

本身允许输入的函数参数在逻辑上等价于函数的返回值,显然处于“输出”位置。


可运行代码

fun main() {
  val access = EADBAccess<AppUserModel>(AppUserModel::class.java)

  access.getSingleDocument("source", "docRef", PrintResult())
}

interface EADBModelI {
  var id: String
}

class AppUserModel : EADBModelI {
  override var id: String = ""
  var name: String = ""

  override fun toString() = "AppUserModel(id='$id', name='$name')"
}

class EADBAccess<in T : EADBModelI>(private val typeParameterClass: Class<T>) {

  fun getSingleDocument(source: String, docRef: String, handler: ResultHandlerI<T>) {
    // simplified example
    val model = AppUserModel()
    model.id = docRef
    model.name = source

    try {
      val result: T = typeParameterClass.cast(model)
      handler.onSuccess(result)
    } catch (e: Exception) {
      handler.onFailure(e)
    }
  }
}

interface ResultHandlerI<T> {
  fun onSuccess(data: T)
  fun onFailure(e: Exception)
}

/** Dummy result handler, prints result to console */
class PrintResult<T> : ResultHandlerI<T> {

  override fun onSuccess(data: T) {
    println("success: $data")
  }

  override fun onFailure(e: Exception) {
    println("failure")
  }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-08-25
    • 1970-01-01
    • 1970-01-01
    • 2021-05-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多