Try-Catch:本质、异常捕获构造及其在移动开发中的工作原理

作者: IT Sectr 发布日期: 2026-05-25 阅读时间: 9 分钟

Try-Catch — 一种异常捕获结构,允许在受保护块中执行潜在危险代码,并在不导致程序崩溃的情况下正确处理错误。try 块包含可能抛出异常的代码,catch 捕获异常并执行恢复逻辑。根据 Apple Swift Documentation (2026)finally 块无论是否抛出异常都会执行,确保资源释放。

要点

  • Try-Catch — 用于捕获异常的结构,防止程序在出错时崩溃
  • try 块包含可能抛出异常的代码 — 执行在第一个错误处中断
  • catch 块捕获指定类型的异常并执行处理或恢复逻辑
  • finally 块在 try/catch 之后保证执行,用于释放资源,例如关闭文件
  • 嵌套的 try-catch允许在一个函数内的不同抽象层次上处理错误

什么是 Try-Catch?

Try-Catch — 结构化异常处理的基本结构,存在于大多数现代编程语言中。它由三个块组成:try(尝试执行危险代码)、catch(捕获和处理异常)以及可选的 finally(终结)。该结构的理念是将业务操作的逻辑与错误处理的逻辑分离,使代码更具可读性和可预测性。

这个概念最初在 C++ 语言中实现为 try/catch,随后被 Java、C#、Swift、Kotlin、Dart、Python、JavaScript 等语言采用。每种语言都增加了自己的特点:在 Swift 中,catch 块必须是穷尽的;在 Kotlin 中,try-catch 可以是表达式(expression);在 Dart 中,finally 对于流资源是必需的。尽管存在差异,基本原则是相同的:错误尽可能在靠近发生地的地方处理,而不是全局处理。

Try-Catch 的使用在移动开发中尤其重要,因为外部因素 — 网络丢失、服务器响应错误、内存不足 — 不断发生。正确的异常处理可以防止应用程序崩溃并确保良好的用户体验:用户会收到错误消息,而不是应用程序突然关闭。根据 Google Android Kotlin Style Guide (2026),每个可能抛出异常的函数都应该通过 try-catch 处理异常,或者在签名中声明 throws。

Try-Catch 的工作原理

Try-catch 的执行机制基于堆栈展开(stack unwinding)。当在 try 块内通过 throw 运算符(或由于系统错误)抛出异常时,正常的执行流程立即中断。执行移动到调用堆栈的更高层,寻找合适的 catch 块。现代语言寻找与抛出异常类型相匹配的 catch,使用类型匹配机制(type matching)。

如果找到相应的 catch,则执行其主体,之后执行在完整的 try-catch-finally 结构之后继续。如果没有找到 catch,异常会在堆栈中继续向上传播,并可能在更高层次上被处理 — 直到全局处理程序,在移动应用程序中它会向用户显示错误对话框。如果异常在任何地方都没有被处理,应用程序将崩溃。这就是为什么对所有可能的异常类型进行正确处理对于应用程序的稳定性至关重要。

基本结构的语法

kotlin
fun readUserData(): User {
    return try {
        val response = api.fetchUser()
        parseUser(response)
    } catch (e: IOException) {
        logError("网络错误", e)
        throw AppException("无法加载数据")
    } catch (e: JsonParseException) {
        logError("解析错误", e)
        return User.default()
    } finally {
        closeLoadingIndicator()
    }
}

代码首先尝试执行 API 请求并解析响应。如果出现 IOException(网络问题),异常被记录并作为 AppException 传播出去。如果出现 JsonParseException — 返回默认用户。finally 块保证隐藏加载指示器,防止 UI 组件在屏幕上泄漏。

Swift 中的 Try-Catch

在 Swift 中,错误处理通过 Error 协议(以前是 ErrorType)实现。任何符合 Error 的类型都可以通过 throw 运算符抛出。可能抛出错误的函数在签名中使用 throws 关键字标记。调用这样的函数需要前缀 try(用于显式的 try-catch)、try?(可选结果)或 try!(强制执行,不进行错误处理)。

swift
enum NetworkError: Error {
    case noConnection
    case serverError(code: Int)
    case timeout
}

func fetchUser(id: Int) throws -> User {
    guard isConnected() else {
        throw NetworkError.noConnection
    }
    let data = try performRequest(path: "/users/\(id)")
    return try decodeUser(from: data)
}

do {
    let user = try fetchUser(id: 42)
    updateUI(user)
} catch NetworkError.noConnection {
    showOfflineAlert()
} catch let error as NetworkError {
    showError("网络 " + error.localizedDescription)
} catch {
    showGenericError()
}

Enum NetworkError 实现了 Error 协议,定义了三种情况:noConnection、带代码的 serverError 和 timeout。fetchUser 函数用 throws 标记:首先检查连接,然后执行请求和解析。在 do-catch 块中,三个 catch 处理不同的场景:特定的 noConnection 情况、通用的 NetworkError 类型以及所有其他错误。这允许根据问题类型向用户显示不同的消息。

Kotlin 中的 Try-Catch

Kotlin 从 Java 继承了 try-catch-finally,但增加了一个重要的区别:在 Kotlin 中,try-catch 是一个表达式(expression),而不是语句(statement)。这意味着 try 块或 catch 块的结果可以赋值给变量。try 块中的最后一个表达式在成功时成为结果,catch 中的最后一个表达式在错误时成为结果。如果没有 catch 处理错误,异常将在堆栈中向上传播。

kotlin
sealed class Result<out T> {
    data class Success<out T>(val data: T) : Result<T>()
    data class Error(val exception: Throwable) : Result<Nothing>()
}

fun loadData(): Result<List<Item>> {
    return try {
        val response = api.getItems()
        Result.Success(response.toList())
    } catch (e: HttpException) {
        Log.e("HTTP ", e)
        Result.Error(e)
    } catch (e: IOException) {
        Log.e("网络 ", e)
        Result.Error(e)
    }
}

在示例中,sealed class Result 封装了成功响应或错误。loadData 函数将 try-catch 用作表达式:成功时返回 Result.Success,在 HttpException 或 IOException 异常时返回 Result.Error 并附带日志记录。这种方法允许调用方无需异常即可处理错误 — 通过基于 Result 类型的 when 表达式。这在 Jetpack Compose 中通过 StateFlow 和 collectAsState 显示不同的 UI 状态(Loading、Success、Error)尤其方便。

Dart 和 Flutter 中的 Try-Catch

Dart 支持 try-catch-finally,语法类似于 Java,但增加了 on 子句,用于在不指定变量的情况下按异常类型进行过滤。当异常本身不需要时 — 只需要知道其类型的事实 — 这很方便。Dart 还支持带有两个参数的 catch 块:异常对象和 StackTrace,这对于记录完整的调用链很有用。

dart
import 'dart:io';
import 'dart:convert';

class UserRepository {
    Future<User> fetchUser(String id) async {
        try {
            final client = HttpClient();
            final request = await client.getUrl(
                Uri.parse('https://api.example.com/users/$id')
            );
            final response = await request.close();
            final body = await response.transform(utf8.decoder).join();
            return User.fromJson(json.decode(body));
        } on SocketException catch (e, stackTrace) {
            log("No internet", e, stackTrace);
            throw AppException("Connection failed");
        } on FormatException {
            throw AppException("Invalid response format");
        } finally {
            client.close();
        }
    }
}

SocketException 被捕获并带有对象和 StackTrace 以进行详细记录,然后作为 AppException 传播。FormatException 被捕获时不带变量 — 知道响应格式不正确就足够了。finally 块保证关闭 HttpClient,防止套接字泄漏。在 Flutter 中,这种方法对于 Widget 测试尤其重要,在 State.initState 中未处理的异常会导致整个测试会话崩溃。

使用 Try-Catch 的常见错误

即使是有经验的开发人员在使用 try-catch 时也会犯错误,导致内存泄漏、隐藏的 bug 或应用程序行为不当。让我们来看移动开发中五个最常见的问题。

空的 catch 块

空的 catch — 最糟糕的做法之一。异常被吞没,应用程序在不正确的状态下继续运行,而开发人员不知道问题。始终至少记录异常。在 Kotlin 中使用 catch(e: Exception) { Log.e(...) },在 Swift 中使用 catch { print($0) }。在 Dart 中,最小可接受的 catch 应该调用 debugPrint 或写入 Crashlytics。

过于宽泛的 catch

通过 catch (Exception e) 捕获所有异常而不按类型区分会隐藏意外错误 — NullPointerException、OutOfMemoryError、StackOverflowError。只捕获您期望并且可以处理的类型。对于其余的类型,允许向上传播。在移动开发中,针对 IOException、TimeoutException、AuthException 的特定 catch 可以为用户提供更有意义的错误消息。

忽略 finally

资源 — 文件、套接字、数据库游标、动画 — 应该在 finally 或 use 块(AutoCloseable)中释放。开发人员经常在发生异常时忘记关闭资源,导致泄漏。在 Kotlin 中,对于 Closeable 资源使用 .use { },在 Swift 中使用 defer { },在 Dart 中使用 async 包中的 await using。finally 块即使在 catch 内部抛出异常时也能保证释放。

线程和协程中的异常

在异步代码中,try-catch 不会捕获来自其他线程的异常。在 Kotlin Coroutines 中使用 CoroutineExceptionHandler 或 SupervisorJob。在 Swift async/await 中 — 在 Task 内部使用 do-catch。在 Flutter 中 — 使用 runZonedGuarded 进行全局捕获。忽视这条规则是生产环境中难以重现的崩溃的原因。

异常时阻塞 UI

异常处理不应无限期地阻塞用户界面。向用户显示具体的消息,并提供重试操作的可能性。Kotlin/Compose 中带有 Retry 按钮的 Snackbar、Swift 中带有 action 的 UIAlertController、Flutter 中带有 action 的 SnackBar — 对于网络或服务器错误,这是最低限度的足够 UX。避免使用没有恢复可能性的通用「发生错误」对话框。

常见问题

Try-Catch 和 Result Type 有什么区别?

Try-catch 使用异常和堆栈展开来处理错误,在错误数量较大时可能会影响性能。Result Type 是一种容器类型(Success 或 Failure),通过模式匹配(pattern matching)进行处理,无需堆栈展开,对于预期错误更加高效。

是否需要在每个 try-catch 中使用 finally?

如果 try 块打开需要关闭的资源(文件、套接字、游标),则 finally 是必需的。如果不打开资源,则不需要 finally。在现代语言中,使用 AutoCloseable/use/defer 自动关闭资源,无需 finally。Kotlin 和 Swift 中的 use 块 替代了 Closeable 对象的 finally。

Try-catch 会变慢吗?

在正常流程中(无异常),try-catch 几乎不影响性能 — JVM 和 Swift 编译器会优化这种情况。但在抛出异常时,会发生堆栈展开,根据堆栈深度可能需要 10–100 微秒。不要使用异常来控制执行流程 — 这是反模式。

如何在 Kotlin Coroutines 中处理错误?

在协程中,使用 coroutineScope 内部的 try-catch 或 CoroutineExceptionHandler 进行全局捕获。SupervisorJob 可防止子协程出错时父协程被取消。对于 launch 使用 CoroutineExceptionHandler,对于 async 在 await() 周围使用 try-catch。

多个 catch 好还是一个 catch 加 if-else 好?

多个 catch 更可取:代码线性可读,每个块处理一种异常类型。一个 catch 加 if-else 更难维护,容易遗漏新的异常类型。在 Swift 中,多个 catch 对于穷尽处理 enum Error 是必需的,在 Kotlin 中没有限制,但最佳实践是每个类型使用单独的 catch。

总结

  • Try-Catch — 带有 try、catch 和可选的 finally 块的异常捕获结构,用于保证资源释放
  • 工作原理 — 抛出异常时进行堆栈展开,根据错误类型寻找匹配的 catch
  • 在 Swift 中使用 do-catch 配合 enum Error,try? 用于可选结果,try! 用于保证成功
  • 在 Kotlin 中try-catch 是一个表达式,其结果可以赋值给变量,与 sealed class Result 配合使用很方便
  • 在 Dart 中支持 on 子句按类型过滤而无须变量,以及 finally 用于关闭 HttpClient
  • 常见错误:空 catch、捕获过于宽泛、忽略 finally 以及协程中缺乏处理
  • 意外错误使用 try-catch,对可能失败的预期场景使用 Result Type

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读