泛型密封类Resource的Flow回调类型匹配及通配符使用问题
问题:优化Resource Flow的回调类型匹配
现有可运行代码
Resource密封类
sealed class Resource<out T:Any>{ data class Success<out T:Any> (val data:T):Resource<T>() data class Error(val exception: Exception):Resource<Nothing>() data class Loading(val message:String):Resource<Nothing>() }
Flow相关代码
fun simple(): Flow<Resource<String>> = flow { delay(100) emit(Resource.Loading("Loading message...")) delay(100) emit(Resource.Error(Exception("Error Message"))) delay(100) emit(Resource.Success("Value=X")) } suspend fun <T> Flow<T>.process( onLoading: ((T)->Unit), onError: ((T)->Unit), onSuccess: ((T)->Unit)) { collect { value -> when ( value ) { is Resource.Error -> { onError(value) } is Resource.Loading -> { onLoading(value) } is Resource.Success<*> -> { onSuccess(value) } } } } fun job1() { viewModelScope.launch { simple().process({ }, { }, { }) } }
需求
当前process函数的三个回调均接收Resource<String>类型,希望实现:
onLoading回调接收Resource.Loading类型onError回调接收Resource.Error类型onSuccess回调接收Resource.Success<String>类型
期望调用形式:
fun job1() { viewModelScope.launch { simple().process( { loading -> /* loading是Resource.Loading类型 */ }, { error -> /* error是Resource.Error类型 */ }, { success -> /* success是Resource.Success<String>类型 */ } ) } }
同时有疑问:编译器强制要求写Resource.Success<*>,否则报错,这样编写是否存在问题?
解决方案
优化后的process扩展函数
通过绑定Flow的泛型与Resource的泛型约束,实现精准的回调类型匹配:
suspend fun <T : Any> Flow<Resource<T>>.process( onLoading: (Resource.Loading) -> Unit, onError: (Resource.Error) -> Unit, onSuccess: (Resource.Success<T>) -> Unit ) { collect { resource -> when (resource) { is Resource.Loading -> onLoading(resource) is Resource.Error -> onError(resource) is Resource.Success -> onSuccess(resource) // 编译器自动推导为Success<T>,无需写<*> } } }
调用示例
fun job1() { viewModelScope.launch { simple().process( onLoading = { loading -> println("加载中:${loading.message}") }, onError = { error -> println("错误:${error.exception.message}") }, onSuccess = { success -> println("成功:${success.data}") } ) } }
关键说明
- 泛型约束调整:将扩展函数的泛型限定为
<T : Any> Flow<Resource<T>>,明确Flow的元素是Resource<T>,让编译器能自动推导Success的具体类型,无需通配符<*>。 - 类型安全保障:密封类的特性让
when分支的类型判断完全可靠,编译器会自动确认每个分支的具体子类类型,无需手动类型转换。
关于Resource.Success<*>的疑问
原代码中必须写Resource.Success<*>,是因为泛型<T>未与Resource的泛型绑定,编译器无法确定Success的具体类型,只能用通配符兜底。这种写法本身合法,但会丢失类型信息——回调中无法直接访问data的具体类型,必须强转,存在运行时类型转换异常的风险。优化后的写法通过泛型绑定解决了这个问题,既保证类型安全,又无需额外转换。
对尝试代码的分析
- Edit_1代码问题:泛型
<T: Resource<Any>>约束过于宽泛,且onSuccess的T无法与Resource.Success的泛型关联,导致编译器无法确认类型匹配,出现类型不兼容报错。 - 强制类型转换的隐患:强转能消除编译报错,但会绕过编译器的类型检查,运行时若类型不匹配会抛出
ClassCastException,不推荐使用。
内容的提问来源于stack exchange,提问作者Metropol_Tilkisi
相关产品推荐
相关产品推荐

