1. 简介

在 Kotlin 中,处理异常的方式有多种。其中两种常见手段是使用语言原生的 try / finally 结构和标准库提供的 runCatching() 函数式封装。本文将深入对比这两种方式的异同,帮助你在实际开发中做出更合理的选择。

⚠️ 虽然它们都能用于异常场景下的控制流管理,但适用场景和行为细节存在关键差异,用错了容易踩坑。

2. 源码层面的相似性

try / finally 是 Kotlin 语言语法的一部分,而 runCatching() 则是标准库中的高阶函数,两者都可用于包裹可能抛出异常的代码块。

先看一个典型的资源管理场景,使用 try / finally

fun tryToDoSomething(): Int {
    var result = 0
    val resource = acquireResource()
    try {
        // 可能抛出异常的操作
        result = performAction(resource)
    } finally {
        releaseResource(resource)
    }
    return result
}

✅ 这里无论 performAction 是否抛异常,finally 块中的 releaseResource 都会被执行,确保资源释放。

换成 runCatching() 实现类似逻辑:

fun doSomethingRunCatching(): Int {
    val resource = acquireResource()
    val result = runCatching {
        // 可能抛出异常的操作
        performAction(resource)
    }
    releaseResource(resource)
    return result.getOrThrow()
}

⚠️ 注意:资源释放写在了 runCatching 外部。虽然看起来简洁,但这里埋了个隐患 —— 如果 getOrThrow() 抛出异常,或其后还有其他代码也抛异常,那么 releaseResource 可能根本不会执行。

📌 runCatching 返回的是一个 Result<T> 对象,通过 .getOrThrow() 获取结果值或重新抛出捕获的异常。

3. 边界情况对比

这是两者最关键的分歧点:执行保障机制不同

✅ try / finally 的优势

  • 无论是否发生异常、return、break、continue 或 throw,finally 块一定会执行
  • 特别适合资源清理(如文件流、数据库连接、锁等)
  • 属于 JVM 层面的保障机制,非常可靠

❌ runCatching 的局限

  • 它本质是对 try/catch 的封装,不支持 finally 语义
  • 清理逻辑必须手动写在外面,无法保证执行
  • 示例中的 releaseResource(resource) 并不能像 finally 那样被强制触发

举个极端例子:

fun riskyWithRunCatching() {
    val conn = openDatabaseConnection()
    val result = runCatching {
        queryData(conn) // 抛异常
    }
    closeConnection(conn) // ❌ 可能不会执行!
    result.getOrThrow()
}

如果 queryData 抛出异常,runCatching 会捕获它并返回 Result.failure。但接下来调用 getOrThrow() 时会重新抛出异常,导致 closeConnection 被跳过 —— 数据库连接泄漏!

⚠️ 所以:当你需要确保某些代码“无论如何都要执行”时,不要依赖 runCatching 后面的语句,应使用 try/finally

4. 如何选择?

场景 推荐方案 原因
✅ 需要资源清理(IO、连接、锁等) try / finally 保证清理逻辑一定执行
✅ 异常需统一捕获并转换为 Result<T> 类型 runCatching 函数式风格,链式调用友好
✅ 简单表达式求值,失败后返回默认值 runCatching { ... }.getOrElse { default } 代码更简洁
✅ 已有 suspend 函数需配合协程异常处理 runCatching + coroutineScope 更易集成

总结一下:

  • 要用 finally 语义?→ 必须用 try/finally
  • 只是想把异常转成 Result 包装?→ runCatching 更优雅

📌 补充:如果你既想用 runCatching 又要保证资源释放,可以结合 use 函数(适用于实现 AutoCloseable 的资源):

fun safeWithRunCatchingAndUse() = runCatching {
    FileInputStream("data.txt").use { stream ->
        stream.readBytes()
    }
}

这样就能兼顾函数式风格和资源安全。

5. 总结

  • try / finally 提供的是执行流程保障,特别适合资源管理和清理任务 ✅
  • runCatching 提供的是异常封装能力,返回 Result<T>,便于函数式编程和链式调用 ✅
  • ⚠️ 两者不可完全互换:runCatching 无法替代 finally 的执行保障
  • 实际项目中应根据是否涉及资源释放来决定使用哪种方式

示例代码已整理至 GitHub:https://github.com/baeldung/kotlin-tutorials/tree/master/core-kotlin-modules/core-kotlin-7


原始标题:Difference Between runCatching and try / finally in Kotlin