错误处理 Result
源:Kotlin standard library - Result
Result<T> 是 Kotlin 标准库提供的一个内联类(Inline Class),用于封装一个可能成功或失败的操作结果。它提供了一种函数式的错误处理风格,旨在取代传统的、且容易导致代码流中断的 try-catch 块。
核心理念
Result 的设计初衷是将“异常”视为一种“值”,通过链式调用来处理成功或失败的分支,使代码逻辑更加线性、平滑。
基础用法
runCatching
这是构建 Result 对象的入口。它会执行代码块,如果成功则封装返回值,如果抛出异常则捕获并封装异常。
kotlin
fun readConfig(path: String): Result<String> = runCatching {
// 这里的代码可能会抛出 IOException
if (path.isEmpty()) throw IllegalArgumentException("Path is empty")
"content"
}获取结果
你可以根据业务需求选择不同的方式来“解包” Result。
kotlin
// 成功返回 T,失败返回 null
val content: String? = result.getOrNull()kotlin
// 失败时返回默认值
val content = result.getOrDefault("default config")kotlin
// 失败时执行 Lambda 计算默认值
val content = result.getOrElse { e ->
logger.warn("Read failed: $e")
"fallback config"
}kotlin
// 成功返回 T,失败重新抛出异常
val content = result.getOrThrow()链式操作 (Functional Chaining)
Result 最大的威力在于支持类似集合的操作符,允许你构建处理流水线。
变换 (map / mapCatching)
kotlin
val result = runCatching { fetchData() }
.map { data ->
// 仅在成功时执行,将 T 转换为 R
parseData(data)
}
.mapCatching { parsed ->
// 如果转换过程也可能抛出异常,使用 mapCatching
validate(parsed)
}恢复 (recover / recoverCatching)
类似 try-catch 中的 catch 块,但在流中处理。
kotlin
val finalResult = operation()
.recover { e ->
// 捕获异常,尝试恢复或返回备用值
if (e is TimeoutException) retry() else throw e
}副作用 (onSuccess / onFailure)
用于执行日志记录、UI 更新等不改变结果值的操作。
kotlin
remoteCall()
.onSuccess { response ->
println("Success: $response")
saveToCache(response)
}
.onFailure { exception ->
println("Error: $exception")
reportError(exception)
}进阶:折叠 (Fold)
当你需要将 Result 最终转换为单一值(无论成功还是失败)时,fold 是最通用的操作符。这在 UI 状态映射中非常有用。
kotlin
val uiState = repository.getData()
.fold(
onSuccess = { data -> UiState.Content(data) },
onFailure = { error -> UiState.Error(error.message) }
)原理与性能
Result 是一个 Inline Class (Value Class)。
kotlin
@JvmInline
value class Result<out T>(internal val value: Any?) : Serializable这意味着在运行时,Result 对象通常不会被分配堆内存,而是直接以内联的方式传递(除非被装箱)。这使得它极其轻量高效。
关键限制与最佳实践
返回类型限制
由于 JVM 的签名冲突问题,Kotlin 编译器曾在很长一段时间内禁止将 Result 作为函数的直接返回类型(除非开启特定编译器参数)。虽然现在放宽了限制,但在公共 API 设计中仍需谨慎。
- 边界使用:推荐仅在系统的“不安全边界”(如 IO 操作、网络请求、第三方库调用)使用
runCatching将异常转换为Result。 - 避免过度包装:不要在核心业务逻辑中层层传递
Result。一旦解包并处理了错误,就应该恢复为领域模型或抛出业务异常。 - 协程异常:在协程中,异常通常用于结构化并发的取消机制。虽然
runCatching可以捕获协程中的异常,但必须小心不要捕获了CancellationException,否则可能导致协程无法被正确取消。
kotlin
// ⚠️ 协程中使用的注意事项
runCatching {
suspendingCall()
}.onFailure { e ->
if (e is CancellationException) throw e // 必须重新抛出!
handleError(e)
}