Kotlin 中的错误处理:try/catch 与高级技术

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

引言:为什么标准的 try/catch 不够用?

\n\n

使用 Kotlin 进行开发,尤其是在服务器端应用或 Android 的背景下,经常需要可靠的错误处理。标准的 try/catch 块是基础,但在实践中,它会导致逻辑分散并增加测试的复杂性。在高级层面上,我们不仅仅是想捕获异常,而是希望设计系统,使错误成为数据类型的一部分,而不是意外的控制流。

\n\n

在本文中,我们将探讨如何超越 try/catch,使用函数式方法:Result、Either(来自库)和 sealed class。您将学习如何使代码可预测、类型安全且易于维护。

\n\n

1. 经典的 try/catch:它什么时候仍然有用?

\n\n

不必完全放弃 try/catch。它对于处理遗留代码、外部库(例如,解析 JSON 时)以及系统调用(输入/输出)是不可或缺的。然而,有一些重要的细微差别。

\n\n

正确的结构和资源管理

\n

与 Java 不同,Kotlin 没有受检异常,但有一个优雅的 use 结构用于自动关闭资源:

\n\n
fun readFile(path: String): String? = try {\n    BufferedReader(FileReader(path)).use { it.readText() }\n} catch (e: IOException) {\n    println("读取文件错误:${e.message}")\n    null\n}
\n\n

这里的 use 保证了文件的关闭,即使内部发生了异常。这是对 try-with-resources 的优雅替代。

\n\n

问题:上下文丢失

\n

try/catch 的主要缺点是它不会告知调用代码该函数可能“失败”。异常是一个隐藏的副作用。调用 readFile() 可能返回 null 或抛出 IOException,但函数签名并未反映这一点。这会导致运行时错误。

\n\n

解决方案是转向显式的错误表示。

\n\n

2. Result:用于成功或失败的内置单子

\n\n

Kotlin 提供了标准的 Result<T> 类。它是一个简单的代数结构:要么是成功(值 T),要么是失败(Throwable)。这使得错误处理成为类型系统的一部分。

\n\n

基本用法

\n
fun riskyDivision(a: Int, b: Int): Result<Int> = runCatching {\n    a / b\n}\n\nfun main() {\n    val result = riskyDivision(10, 0)\n    result.onSuccess { println("结果:$it") }\n         .onFailure { println("错误:${it.message}") }\n}
\n\n

runCatching 是 try/catch 的一个包装器,它返回一个 Result。方法 onSuccess 和 onFailure 允许您对结果做出反应,而不会中断调用链。

\n\n

转换和链式调用

\n

您可以转换结果,而无需担心 map 内部的异常:

\n\n
val finalResult = riskyDivision(10, 2)\n    .map { it * 2 }          // 如果成功——乘以 2\n    .recover { error ->      // 如果错误——返回备用值\n        println("由于 ${error.message},使用备用值")\n        0\n    }\nprintln(finalResult.getOrDefault(-1)) // 10
\n\n

重要提示: Result 不适合在没有额外处理的情况下存储在集合中或在线程之间传递。对于这些目的,sealed class 或像 Arrow 这样的库更合适。

\n\n

3. Either:区分错误类型

\n\n

Result 类只固定了 Throwable。但我们经常希望区分错误类型:“未找到”、“权限不足”、“验证错误”。这时,来自函数式库(例如 Arrow 或自定义

博客

书籍推荐