【问题标题】:Mixing class and function generics in Kotlin?在 Kotlin 中混合类和函数泛型?
【发布时间】:2017-04-17 12:48:53
【问题描述】:

我正在尝试创建一个定义单个方法的接口。该方法的参数类型必须是接口的类型参数,因为接口的实现将对该参数具有唯一的实现。到目前为止很好,但要注意的是参数还有一个与方法相关的类型参数。我可以将一个类型的参数定义为类类型参数,它也有自己的类型参数,该类型参数与方法相关联吗?

换一种说法:

我正在尝试构建一个具有两种实现的 api 客户端接口。每个客户端都基于传递给他们的rxecute 函数的Request<T> 对象进行api 调用(RxJava + 执行,是的,我为这个名字感到过分自豪)。

我的问题是如何在界面中定义rxecute 方法。让我列出一些示例代码(我的实际代码没有使用impl,别担心)。

interface Request<T> // T is return type of the request

class RequestImpl<T>: Request<T> { // impl }

interface ApiClient<R : Request<*>> {
  // R should somehow be of type Request<T>
  fun <T> rxecute(request: R): Single<T>
}

class ApiClientImpl : ApiClient<RequestImpl<????>> {
  override fun <T> rxecute(request: RequestImpl<T>): Single<T> = // impl
}

我希望能够像这样使用它:

val apiClient = ApiClientImpl()
apiClient.rxecute(RequestImpl<SomeClass>()).subscribe(someClassResult -> ...)

是否可以创建这样的接口或者泛型不能那样工作?

【问题讨论】:

    标签: generics kotlin


    【解决方案1】:

    不幸的是,Kotlin 泛型不能以这种方式进行操作;类型参数R本身不能作为泛型类型(R&lt;T&gt;),其上限在声明处是固定的,只能使用类型参数R作为单个上界(没办法要添加另一个,where Q : R, Q : Request&lt;T&gt; 不起作用)。

    您只能参数化具体类型,我想到的一个选项是recursive generics 模式(它也用于 Java)。您可以将实现类型的另一个类型参数添加到Request,使其变为interface Request&lt;R, T&gt;。这需要您将实现的类型添加到继承的类型声明中:

    class RequestImpl<T>: Request<RequestImpl<*>, T> { /* impl */ }
    

    然后修改ApiClient:

    interface ApiClient<R : Request<R, *>> {
        fun <T> rxecute(request: Request<R, T>): Single<T>
    }
    

    这里出现的一个不受欢迎的副作用是您需要强制转换 request 才能将其用作 RequestImplrequest as RequestImpl

    并将ApiClientImpl 变换如下:

    class ApiClientImpl : ApiClient<RequestImpl<*>> {
        override fun <T> rxecute(request: Request<RequestImpl<*>, T>): Single<T> = TODO()
    }
    

    此解决方案看起来比您要求的要冗长得多,但递归泛型似乎是此类情况的常见解决方法,这需要比 Java 和 Kotlin 提供的更强大的类型系统。


    (runnable demo of the code in the answer)

    【讨论】:

    • 我以前没有见过递归泛型,但这绝对是个坏事。更重要的是,它编译。在运行整个程序之前,我需要修复一些其他代码,但这看起来不错。小细节;您现在还需要投射(request as RequestImpl&lt;T&gt;),这有点难看但很安全。我会在修复我的东西并确认它正常工作后立即将其标记为已回答。
    • @DavidWhitman,是的,request as RequestImpl&lt;T&gt; 是使用递归泛型时的已知难题。您可以在不失去类型安全性的情况下稍微缩短它:request as RequestImpl&lt;T&gt; 在运行时无论如何都不会检查,如果它在编译时匹配,那么它仍然是安全的)
    • 出色的答案,代码运行良好。 Kotlin 是一门漂亮的语言,尤其是在与 Java 相对时;非常感谢您为此所做的工作。还有一点奇怪,我有一个解决方法,但其他人可能想知道。我为ApiClient 创建了一个带有签名class ApiClientWrapper&lt;out C : ApiClient&lt;R&gt;, R : Request&lt;R, *&gt;&gt; 的包装器。它使用fun &lt;T&gt; rxecute(request: R): Single&lt;T&gt; 包装rxecute 调用,但是您需要进行不安全的强制转换以将结果映射到T.map { it as T }),因为request: R 的类型参数为@ 987654348@.
    猜你喜欢
    • 2016-09-28
    • 2015-11-04
    • 2021-12-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-22
    • 2015-07-11
    • 1970-01-01
    相关资源
    最近更新 更多