Dart:异常

Exception和Error

基本区别

特性 Exception Error
含义 程序可以预期并处理的异常情况 通常不可恢复的严重问题
用途 业务逻辑错误、输入校验失败、网络超时等 程序 bug、内存溢出、类型错误等
处理方式 通常会用 try-catch 捕获并恢复 很少捕获,通常要修复代码本身
继承关系 继承自 Exception 继承自 Error(也实现了 Exception 接口)
示例 FormatException, IOException TypeError, OutOfMemoryError

Exception —— 可预期异常

  • 可控的,发生后程序依然能继续运行。
  • 通常用于表示业务错误外部环境问题
void parseAge(String input) {
  var age = int.parse(input);
  if (age < 0) {
    throw Exception("年龄不能为负数");
  }
}

void main() {
  try {
    parseAge("-5");
  } catch (e) {
    print("捕获到异常: $e");
  }
}

Error —— 不可恢复的错误

  • 通常代表代码逻辑或系统级错误
  • 即使捕获了,也很难安全恢复。
void causeError() {
  var list = [1, 2, 3];
  print(list[5]); // RangeError
}

void main() {
  try {
    causeError();
  } catch (e, s) {
    print("捕获到错误: $e");
    print("堆栈信息: $s");
  }
}

统一处理

在 Dart 中 Error 其实也实现了 Exception 接口,所以 catch (e) 可以同时捕获 ExceptionError

如果你想区分:

try {
  // 可能出错的代码
} on Exception catch (e) {
  print("这是 Exception: $e");
} on Error catch (e) {
  print("这是 Error: $e");
}

Dart 的异常处理机制

Dart 通过 抛出(throw)捕获(try-catch-finally) 来处理异常。

Dart 中所有对象(不仅仅是 ExceptionError)都可以作为异常抛出,不过推荐抛 Exception 或其子类。

Dart 没有 Java 那样的 checked exception(受检异常),所以函数声明时不用写 throws

抛出异常

一般使用

void validateAge(int age) {
  if (age < 0) {
    throw Exception('年龄不能为负数');
  }
}

void main() {
  validateAge(-5); // 会抛出异常
}

在 Dart 里 throw 后面可以是任何对象,不一定非得是 ExceptionError。不过这么做在实际项目里很少见,因为会降低代码可读性。

抛出字符串

void main() {
  try {
    throw '这是一个字符串异常';
  } catch (e) {
    print('捕获到异常: $e'); // 捕获到异常: 这是一个字符串异常
    print('类型: ${e.runtimeType}'); //类型: String
  }
}

抛出自定义对象

class MyProblem {
  final String message;
  MyProblem(this.message);

  @override
  String toString() => 'MyProblem: $message';
}

void main() {
  try {
    throw MyProblem('配置文件缺失');
  } catch (e) {
    print('捕获: $e'); // 捕获: MyProblem: 配置文件缺失
    print('类型: ${e.runtimeType}'); // 类型: MyProblem
  }
}

抛出数字

void main() {
  try {
    throw 404;
  } catch (e) {
    print('捕获到异常: $e'); //捕获到异常: 404
    print('类型: ${e.runtimeType}'); //类型: int
  }
}

自动向上抛出异常

Dart 的异常传播是沿调用栈从下向上冒泡的过程。

Dart 异常是 向调用栈上抛 的,不会像 Java 那样要求声明 throws

只有遇到第一个匹配的 try-catch 才会停止冒泡。

如果最外层(main)也没捕获,程序会直接终止并输出未捕获异常。

调用开始:
┌───────────────────────────────┐
│ main()                        │  <-- 最外层
│   调用 method1()               │
│     调用 method2()             │
└───────────────────────────────┘

执行到 method2():
┌───────────────────────────────┐
│ main()                        │
│   调用 method1()               │
│     调用 method2()             │
│       throw FormatException()  │  <-- 异常发生
└───────────────────────────────┘

异常冒泡过程:
1. method2() 没有 try-catch → 异常抛给 method1()
2. method1() 没有 try-catch → 异常抛给 main()
3. main() 有 try-catch → 捕获并处理

异常传播方向(从下往上):
method2()  ❌ →  method1()  ❌ →  main() ✅

最终结果:
main 捕获到异常: FormatException: 格式错误

捕获异常

try-catch

void main() {
  try {
    int.parse('abc'); // FormatException
  } catch (e) {
    print('捕获异常: $e');
  }
}
  • 没有 on,会捕获所有异常(包括 ErrorException)。
  • 如果需要更精细的类型处理,就要手动判断类型。

try-on

try {
  throw OutOfMemoryError(); // 模拟内存不足错误");
} on OutOfMemoryError {
  print('捕获到异常: $e'); // 捕获到异常: 2.718281828459045
  print('没有内存了');
  // rethrow;
} catch (e) {
  print('捕获到异常: $e'); // 捕获到异常: OutOfMemoryError
  print(e);
}
  • on Type 只做 类型匹配,不接收异常对象。

  • 如果你不需要访问异常对象(比如只打印固定的提示),这种写法最简洁。

  • 不能直接在里面用 e,因为没声明变量。

try-on-catch(指定类型)

void main() {
  try {
    int.parse('abc');
  } on FormatException catch (e) {
    print('格式错误: $e');
  } on Exception catch (e) {
    print('其他异常: $e');
  }
}
  • on Type catch (e) 也是按类型匹配,但同时会接收异常对象到 e 里。
  • 如果需要使用异常信息、堆栈等,必须这样写。

三个的对比

写法 匹配方式 能否访问异常对象 场景
on Type { ... } 按类型 只关心类型,不关心异常内容
on Type catch (e) 按类型 关心类型且需要异常信息
catch (e) 所有异常 最通用的捕获

获取堆栈信息

void main() {
  try {
    int.parse('abc');
  } catch (e, stack) {
    print('异常: $e');
    print('堆栈: $stack');
  }
}

finally 语句块

finally 无论是否发生异常都会执行,适合做资源清理:

void main() {
  try {
    int.parse('abc');
  } catch (e) {
    print('捕获异常: $e');
  } finally {
    print('不管异常与否,这里都会执行');
  }
}

rethrow(重新抛出)

有时你捕获异常后,想在当前层做部分处理,然后继续让上层处理,可以用 rethrow

void main() {
  try {
    handle();
  } catch (e) {
    print('main 捕获: $e');
  }
}

void handle() {
  try {
    int.parse('abc');
  } catch (e) {
    print('handle 捕获并记录日志');
    rethrow; // 继续抛给上层
  }
}

异步异常处理

async/await 中的异常用普通的 try-catch 捕获:

Future<void> loadData() async {
  try {
    await Future.delayed(Duration(seconds: 1));
    throw Exception('网络错误');
  } catch (e) {
    print('捕获到: $e');
  }
}

如果用 then/catchError

Future.delayed(Duration(seconds: 1))
    .then((_) => throw Exception('网络错误'))
    .catchError((e) => print('捕获: $e'));

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1909773034@qq.com

×

喜欢就点赞,疼爱就打赏