开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 26 天,点击查看活动详情
用Kotlin Result编程: kotlin.Result
对处理Result的最重要的标准库函数的简短浏览 - 使用fold()进行一般转换, 使用getOrThrow(), getOrElse()/getOrDefault()检索值, 使用map()/mapCatching()映射成功, 使用recover()/recoverCatching()映射失败和使用onSuccess()/onFailure()窥探.
kotlin.Result类型被添加到标准库中, 以解决我们在以前文章中谈到的问题, 除此之外. 它的实现与我们在上一节介绍的有点不同 - 它不是一个密封的类层次结构, 而是作为一个单一的值类来实现.
除此之外, 它与我们自己设计的基本相同 - 它在语义上仍然等同于Result = Success(T) | Failure, 尽管它没有以这种方式实现.
要手动创建一个成功或失败的值, 请使用定义在同伴对象上的Result.success(value: T)和Result.failing(exception: Throwable)方法. 你可以使用isSuccess和isFailure方法确定Result是成功还是失败, 并使用getOrNull()和exceptionOrNull()访问该值/Throwable. 然而, 你很少需要这些, 因为它们提倡更多的命令式编程风格, 而应该选择下面描述的标准函数之一.
标准库还定义了一个runCatching方法, 与我们之前使用的方法完全相同.
还有一个接收器版本, T.runCatching. 基本上, T.runCatching对T.run来说就像runCatching对run一样:
标准函数
所有标准化的函数都是以下函数的专门版本:
inline fun <T, R> Result<T>.fold(
onSuccess: (value: T) -> R,
onFailure: (exception: Throwable) -> R
): R
这将改变Result中包含的值, 取决于它是成功还是失败. 实质上, 如果Result是成功的, 该函数返回 onSuccess(this.getOrNull()!!), 否则返回onFailure(this.exceptionOrNull()!).
检索值
getOrThrow()
fun <T> Result<T>.getOrThrow(): T
返回成功值, 或者抛出. 虽然这看起来是一个微不足道的函数, 但在组成返回Result的方法时, 它实际上是非常有用的, 而且这个主题非常有趣, 我们将用一个单独的章节来介绍它.
等价于: fold({ it }, { throw it }).
getOrElse(), getOrDefault()
inline fun <T, R> Result<T>.getOrElse(
onFailure: (exception: Throwable) -> R
): R fun <T, R> Result<T>.getOrDefault(defaultValue: R): R
返回成功值, 或将异常映射为一个/返回一个默认值:
等价于: fold({ it }, ::onFailure)/fold({ it }, { defaultValue })
映射成功的Result
map()
inline fun <T, R> Result<T>.map(
transform: (value: T) -> R
): Result<R>
转化成功的值. 在transform内部抛出的异常将会重抛.
等价于: fold({ Result.success(transform(it)) }, { Result.failure(it) })
mapCatching()
inline fun <T, R> Result<T>.mapCatching(
transform: (value: T) -> R
): Result<R>
转化成功的值, 在transform内部抛出的异常将会转化成失败.
等价于: fold({ runCatching { transform(it) } }, { Result.failure(it) })
你应该使用哪种方法?
只在转换一个单个结果时使用map/mapCatching. 我们将在下一篇文章中讨论合并和合成多个结果.
如果转换的业务/技术本质不能导致失败(例如, 将一个值乘以2), 则使用map. 如果转换的本质可以导致失败(即保存记录, 执行业务计算等), 则使用mapCatching.
我强调转换的本质, 因为从理论上讲, 每一行都可以抛出OutOfMemory, ThreadDeath等错误. 用 runCatching来包装每一行真的没有意义 - 这些错误, 如果它们真的发生在做乘以2这样的小事时, 会被该方法的上游调用者抓住.
转换失败的结果
recover()
inline fun <T, R> Result<T>.recover(
transform: (exception: Throwable) -> R
): Result<R>
转化一个失败值(R需要可分配给T). 与map类似, 但对失败进行操作. 和map一样, 在transform中抛出的异常会被重新抛出.
等价于: fold({ Result.success(it) }, { Result.success(transform(it)) })
recoverCatching()
inline fun <T, R> Result<T>.recoverCatching(
transform: (exception: Throwable) -> R
): Result<R>
转化一个失败值, 将transform中抛出的任何异常转换为失败. 与mapCatching类似, 但对失败进行操作.
你应该使用哪种方法?
和map一样, 如果转换是原子性的和/或转换的业务/技术本质不包括失败, 则使用recover. 如果转换是复杂的, 和/或转换的本质包括失败, 则使用recoverCatching.
窥探
onSuccess(), onFailure()
inline fun <T> Result<T>.onSuccess(
action: (value: T) -> Unit
): Result<T>inline fun <T> Result<T>.onFailure(
action: (exception: Throwable) -> Unit
): Result<T>
如果Result是一个成功/失败的结果, 就执行动作. 返回原始的Result, 不做任何改变. 这些函数基本上等同于also, 但在Result的上下文中. 它们的使用情况是一样的.
开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 26 天,点击查看活动详情