You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin中ResultWrapper类泛型实现的两处疑问咨询

Kotlin ResultWrapper泛型实现详解

先确认你的初始理解:完全正确。

  • T确实是成功场景下预期接收的响应模型;
  • 错误场景(GenericError/NetworkError)不会返回有效成功数据,用Nothing(所有类的子类,表示无返回值)是合理的;
  • 给ResultWrapper加上out T实现协变,能让ResultWrapper<Nothing>成为任意ResultWrapper<T>的子类型,这样safeApiCall不管是成功返回Success<T>,还是出错返回NetworkError/GenericError,都能统一返回ResultWrapper<T>类型,完美适配函数的返回值要求。

疑问1:为什么Success要声明为协变子类?

先看Success的定义:

data class Success<out T>(val value: T): ResultWrapper<T>()

这是为了保持整个ResultWrapper体系的协变一致性。父类ResultWrapper已经用out T声明了协变,作为它的子类,Success的泛型参数必须也声明为协变,否则会破坏整个类型体系的协变特性。

举个例子:假设我们有Animal和它的子类Dog,如果Success没有out T,那么Success<Dog>就无法被当成ResultWrapper<Animal>使用——但父类ResultWrapper的协变设计本来就是允许这种向上转型的。只有给Success加上out T,才能让Success<Dog>成为ResultWrapper<Animal>的合法实例,保证协变特性在整个密封类的所有子类中生效。

另外,虽然Success持有T,但这里的out是为了和父类的协变规则对齐,T在Success中是只读的val,属于输出位置,完全符合协变的使用要求。


疑问2:协变的T只能用于输出位置,为什么safeApiCall能把T当参数用?

这里要区分两个T:

  • 一个是ResultWrapper<out T>中的协变泛型T,它的限制是在ResultWrapper类内部,T只能出现在输出位置(比如返回值、只读属性);
  • 另一个是safeApiCall函数自己的泛型T,这是独立的泛型参数,和ResultWrapper的T只是名字相同而已。

看safeApiCall的逻辑:

suspend fun <T> safeApiCall(dispatcher: CoroutineDispatcher, apiCall: suspend () -> T): ResultWrapper<T> {
    return withContext(dispatcher) {
        try {
            ResultWrapper.Success(apiCall.invoke())
        } catch (throwable: Throwable) {
            when (throwable) {
                is IOException -> ResultWrapper.NetworkError
                is HttpException -> {
                    val code = throwable.code()
                    val errorResponse = convertErrorBody(throwable)
                    ResultWrapper.GenericError(code, errorResponse)
                }
                else -> {
                    ResultWrapper.GenericError(null, null)
                }
            }
        }
    }
}

这里的apiCall是suspend () -> T,也就是它的返回值是T,我们把这个返回值传入Success的构造函数——而Success的value是只读的val,属于输出位置(我们只能从Success中读取value,不能修改它),这完全符合ResultWrapper协变的规则。协变的限制从来不是“外部不能把T传入ResultWrapper”,而是“ResultWrapper内部不能把T作为输入位置(比如函数参数、可变属性)”,所以这里的用法是完全合法的。


内容的提问来源于stack exchange,提问作者Android Developer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 18:18:34