为何网络响应类NetworkResponse需要使用<out T>(协变)?
关于NetworkResponse密封类协变的必要性及函数报错原因
定义的密封类及问题场景
我定义了如下带<out T>标记的网络响应密封类:
sealed class NetworkResponse<out T> { data class Success<T>(val value: T): NetworkResponse<T>() data class GenericError(val code: Int? = null, val error: String? = null): NetworkResponse<Nothing>() object NetworkError: NetworkResponse<Nothing>() class UnknownError(message: String? = "Unknown Error") : NetworkResponse<Nothing>() }
当我把其中的out T替换成T后,下面的call函数会报错。请解释为什么这段代码需要<out T>(协变),以及函数报错的原因:
suspend fun <T> call(dispatcher: CoroutineDispatcher, apiCall: suspend () -> Response<T>): NetworkResponse<T> { return withContext(dispatcher) { try { val data = apiCall.invoke() if (data.isSuccessful) { data.body()?.let { NetworkResponse.Success(it) } ?: NetworkResponse.UnknownError() } else { NetworkResponse.NetworkError } } catch (throwable: Throwable) { when (throwable) { is IOException -> { Log.e("IOException", throwable.message?: "") NetworkResponse.NetworkError } is HttpException -> { Log.e("HttpException", throwable.message?: "") val code = throwable.code() val errorResponse = throwable.message() NetworkResponse.GenericError(code, errorResponse) } else -> { Log.e("GenericException", throwable.message?: "") NetworkResponse.GenericError(null, null) } } } } }
一、为什么需要<out T>(协变)
协变out T的核心作用是让子类类型的对象能安全赋值给父类类型变量,放到这个网络请求场景里:
Nothing是Kotlin中所有类型的子类型,所以NetworkResponse<Nothing>自然是所有NetworkResponse<T>的父类- 你定义的几个错误状态(
GenericError、NetworkError、UnknownError)都继承自NetworkResponse<Nothing>,而成功状态Success<T>对应具体业务类型的NetworkResponse<T> - 加上
out T后,这些通用的错误对象就能被当作任意NetworkResponse<T>返回——这完全符合网络请求的逻辑:请求要么成功返回对应类型的数据,要么返回通用的错误结果,错误结果不需要和成功类型绑定。
二、去掉协变后函数报错的原因
把out T换成T后,NetworkResponse变成了不变类型,类型匹配规则会变得非常严格:
- 函数的返回类型是
NetworkResponse<T>,但你代码里返回的NetworkError、GenericError、UnknownError都是NetworkResponse<Nothing> - 在不变类型规则下,
NetworkResponse<Nothing>不能直接赋值给NetworkResponse<T>(除非T就是Nothing,但这里T是泛型参数,代表任意业务类型) - 举个具体例子:当请求失败时你返回
NetworkResponse.NetworkError,这是NetworkResponse<Nothing>,但函数要求返回NetworkResponse<T>,两者类型不兼容,编译器就会报错。
说白了,协变就是给错误状态开了个“绿灯”,让它们能兼容任意NetworkResponse<T>的返回要求;去掉协变后这个绿灯没了,类型不匹配的问题就暴露出来了。
内容的提问来源于stack exchange,提问作者Hack123
相关产品推荐
相关产品推荐

