Kotlin中如何封装带具体化类型参数的内联方法withCatchingErrors
reified泛型的私有inline函数 你遇到的核心限制是:Kotlin的public inline函数无法直接调用private inline函数——因为inline函数的代码会被内联到外部调用方的作用域,而private函数对外部不可见。而你的withCatchingErrors()因需要reified泛型做类型检查,必须保留inline关键字。以下是三种可行的封装方案:
方案1:将withBreakableFlow()改为非inline函数(优先推荐,如果允许)
如果withBreakableFlow()不需要是inline函数(比如不需要支持非局部返回等inline专属特性),直接将其设为public non-inline,同时保留withCatchingErrors()为private inline即可。非inline函数在类内部调用private inline函数完全合法,内联仅发生在类内部代码中,外部调用方不会接触到私有逻辑。
object FlowUtils { // 公开的非inline方法 public fun <T> withBreakableFlow( block: suspend FlowBreakScope.() -> T ): Flow<T> = flow { val scope = object : FlowBreakScope { override fun <E> breakFlow(error: E) { throw FlowBreakException(error) } override fun <E> catchError(block: () -> Unit) { // 内部调用私有inline函数 withCatchingErrors<E>(block) } } emit(block(scope)) } // 私有inline函数,带reified泛型做类型检查 private inline fun <reified E> withCatchingErrors(action: () -> Unit) { try { action() } catch (e: AppErrorAsThrowable) { if (e.data is E) { throw FlowBreakException(e.data) } else { throw e } } } } // 辅助定义 interface FlowBreakScope { fun <E> breakFlow(error: E) fun <E> catchError(block: () -> Unit) } class FlowBreakException(val error: Any?) : Throwable() class AppErrorAsThrowable(val data: Any?) : Throwable()
方案2:将withCatchingErrors()定义为局部inline函数
如果withBreakableFlow()必须是inline函数,可以把withCatchingErrors()作为前者的局部inline函数。局部函数仅在包含它的函数内部可见,同时因为是inline的,reified泛型可以正常工作;public inline函数内联时会将局部函数的代码一并包含进去,外部完全无法访问这个私有逻辑。
object FlowUtils { // 公开的inline方法 public inline fun <T> withBreakableFlow( crossinline block: suspend FlowBreakScope.() -> T ): Flow<T> = flow { // 局部inline函数,仅当前方法内部可见 inline fun <reified E> withCatchingErrors(action: () -> Unit) { try { action() } catch (e: AppErrorAsThrowable) { if (e.data is E) { throw FlowBreakException(e.data) } else { throw e } } } val scope = object : FlowBreakScope { override fun <E> breakFlow(error: E) { throw FlowBreakException(error) } override fun <E> catchError(block: () -> Unit) { withCatchingErrors<E>(block) } } emit(block(scope)) } } // 辅助定义同上 interface FlowBreakScope { fun <E> breakFlow(error: E) fun <E> catchError(block: () -> Unit) } class FlowBreakException(val error: Any?) : Throwable() class AppErrorAsThrowable(val data: Any?) : Throwable()
方案3:用@PublishedApi标记为internal(模块化场景)
如果你的代码是模块化的,且希望在模块内复用withCatchingErrors()但对外隐藏,可以用@PublishedApi注解将其标记为internal。这个注解允许public inline函数访问internal成员,同时外部模块无法直接调用该函数,达到模块级封装效果。
object FlowUtils { // 公开的inline方法 public inline fun <T> withBreakableFlow( crossinline block: suspend FlowBreakScope.() -> T ): Flow<T> = flow { val scope = object : FlowBreakScope { override fun <E> breakFlow(error: E) { throw FlowBreakException(error) } override fun <E> catchError(block: () -> Unit) { withCatchingErrors<E>(block) } } emit(block(scope)) } // @PublishedApi允许inline函数访问,对外(模块外)不可见 @PublishedApi internal inline fun <reified E> withCatchingErrors(action: () -> Unit) { try { action() } catch (e: AppErrorAsThrowable) { if (e.data is E) { throw FlowBreakException(e.data) } else { throw e } } } } // 辅助定义同上
内容的提问来源于stack exchange,提问作者K0cur

