# Dart教程 - 11 异常

# 11.1 异常的概念

什么是异常?

异常(Exceptions)是指在程序执行过程中出现的错误或异常情况,导致程序无法继续正常执行的事件。

Dart 提供了异常处理机制来捕获和处理这些异常。

例如:整数除数是0会抛出异常。

void main() {
  int i = 5 ~/ 0;
  print(i);
}
1
2
3
4

执行的时候就会发生错误:

当程序出现错误的时候,我们通常称之为:抛出异常。

程序抛出异常就无法继续执行了,但是任何程序都不可能是完美的没有bug的,只能尽可能的对错误进行预防和处理。

# 11.2 捕获异常

对异常进行预防和提前处理,这种行为通常称之为异常捕获。

一个程序出现任何错误,就停止运行肯定不是我们希望看到的,即使程序出现错误,哪怕给与用户一个错误提示,也是一种积极的处理方式。

# 1 异常捕获语法

最简单的异常捕获语法:

try {
  // 可能会抛出异常的代码
} catch (e) {
  // 捕获并处理异常
}
1
2
3
4
5

举个栗子:

import 'dart:convert';

void main() {
  try {
    String jsonString = 'Hello';
    // 将字符串转换为字典
    Map<String, dynamic> data = jsonDecode(jsonString);

    print("无法被指定到的代码");
  } catch (e) {
    // 捕获并处理异常
    print("JSON字符串格式不正确");
  }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

上面的程序执行到 jsonDecode(jsonString) 会抛出异常,然后执行 catch 块中的语句。出现异常后,在出现异常语句后的代码是无法被继续执行的。

执行结果:

JSON字符串格式不正确

这样在发生错误的时候,可以给用户一个提示。当然具体的处理方式需要根据业务需求的处理,这里只是举个例子。

# 2 打印异常详细信息

捕获异常后,在catch块中,可以通过异常对象和stacktrace对象打印异常的详细信息。

import 'dart:convert';

void main() {
  try {
    String jsonString = 'Hello';
    // 将字符串转换为字典
    Map<String, dynamic> data = jsonDecode(jsonString);
  } catch (e, stackTrace) {
    print("JSON字符串格式不正确");
    print('发生异常: $e');
    print('堆栈轨迹:\n$stackTrace');
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

执行结果:

# 3 捕获指定类型的异常

在程序运行时,可能会出现不同类型的异常,可能需要对不同类型的异常进行不同的处理,这个时候就需要根据类型来捕获异常了。

语法:

try {
  String jsonString = 'Hello';
  // 将字符串转换为字典
  Map<String, dynamic> data = jsonDecode(jsonString);
} on FormatException catch (e) {
  // 捕获并处理异常
  print("JSON字符串格式不正确");
  print("异常信息:$e");
}
1
2
3
4
5
6
7
8
9

上面捕获了 FormatException 类型的异常,在捕获异常后,打印了异常信息和提示信息。

如果不想获取异常信息,还可以省略 catch (e) :

try {
  String jsonString = 'Hello';
  // 将字符串转换为字典
  Map<String, dynamic> data = jsonDecode(jsonString);
} on FormatException {
  // 捕获并处理异常
  print("JSON字符串格式不正确");
}
1
2
3
4
5
6
7
8

需要注意,上面只是捕获了 FormatException 类型的异常,如果有代码抛出了其他类型的异常,是无法捕获的。

举个栗子,我们修改代码如下:

try {
  // 截取字符串
  String subStr = "Hello".substring(10);
  print("子字符串:$subStr");

  String jsonString = 'Hello';
  // 将字符串转换为字典
  Map<String, dynamic> data = jsonDecode(jsonString);
} on FormatException {
  // 捕获并处理异常
  print("JSON字符串格式不正确");
}
1
2
3
4
5
6
7
8
9
10
11
12

当截取字符串超过了字符串的长度,就会抛出异常,但是我们没有捕获这个异常,导致程序终止。

所以针对这种情况需要捕获多个异常。

# 4 捕获多个异常

我们可以同时捕获多个异常,并对每种异常可以采用不同的处理。

举个栗子:

import 'dart:convert';

void main() {
  try {
    // 截取字符串
    String subStr = "Hello".substring(10);
    print("子字符串:$subStr");

    String jsonString = 'Hello';
    // 将字符串转换为字典
    Map<String, dynamic> data = jsonDecode(jsonString);
  } on FormatException catch (e) {
    // 捕获并处理异常
    print("JSON字符串格式不正确");
    print("错误信息:$e");
  } on RangeError catch (e) {
    print("截取字符串越界");
    print("错误信息:$e");
  } catch (e) {
    print("错误信息:$e");
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

如果我们不能判断是否还会抛出其他类型的错误,可以在最后添加 catch (e) 用来捕获其他类型的错误。

# 5 异常finally

finally表示的是无论是否发生异常都要执行的代码。

举个栗子:

try {
  String jsonString = 'Hello';
  // 将字符串转换为字典
  Map<String, dynamic> data = jsonDecode(jsonString);
} catch (e) {
  // 捕获并处理异常
  print("JSON字符串格式不正确");
} finally {
  print("出现异常也会被执行");
}
1
2
3
4
5
6
7
8
9
10

执行结果:

JSON字符串格式不正确 出现异常也会被执行

finally一般在文件读写操作的时候用的比较多,就是不管是否发生错误都要关闭文件,所以可以将文件的关闭操作放在finally块中。

# 11.3 异常的传递

什么是异常的传递?

异常的传递,就是当函数或方法执行的时候出现异常,如果没有进行捕获,就会将异常传递给该函数或方法的调用者,如果调用者仍未处理异常,则继续向上传递,直到传递给主程序,如果主程序仍然没有进行异常处理,则程序将被终止。

举个栗子:

import 'dart:convert';

void parseJson() {
  String jsonString = 'Hello';
  // 将字符串转换为字典
  Map<String, dynamic> data = jsonDecode(jsonString);
}

void callFun() {
  // 调用上面的input_fun()
  parseJson();
}

void main() {
  try {
    parseJson();

    print("无法被指定到的代码");
  } catch (e) {
    // 捕获并处理异常
    print("JSON字符串格式不正确");
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

上面代码,执行parseJson会抛出异常,因为parseJson()函数中没有进行异常处理,则异常会抛给callFun()函数,callFun()没有进行异常处理,则抛给main()函数,main()函数中则对异常进行了处理,程序不会崩溃。

利用异常的传递性,如果我们在main()函数中进行了异常捕获,无论程序哪里发生了错误,最终都会被传递到main()函数中,保证所有的异常都会被捕获。但是不要所有的异常都在main函数中处理,main函数中的异常处理只是兜底处理。

# 11.4 主动抛出异常

除了代码执行的时候出错,系统会主动抛出异常,我们还可以根据实际的业务需要,主动抛出异常。

首先创建一个异常对象,然后使用 raise 关键字抛出异常对象。

举个栗子:

import 'dart:io';

String inputUsername() {
  print("请输入用户名:");
  // 读取键盘输入
  var name = stdin.readLineSync();

  if (null == name || name.length > 16 || name.length < 8) {
    throw Exception("用户名格式错误"); // 主动抛出异常
  }

  return name;
}

void main() {
  try {
    var password = inputUsername();
    print("输入的用户名:$password");
  } catch (e) {
    print("用户名格式错误,请重新输入");
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

当输入abc的时候,执行结果:

请输入用户名:abc 用户名格式错误,请重新输入

# 11.5 自定义异常类

除了使用 Dart 内置的异常类型外,您还可以自定义异常类,以便更好地描述特定的异常情况。自定义异常类通常继承自Exception或其子类。建议以Exception结尾,见名知意。

举个栗子:

下面我们首先自定义了两个异常类,然后根据需要抛出这两个异常类的对象,在捕获异常的时候,根据异常类型进行不同的处理。

并在自定义异常类中可以封装自定义的参数。

import 'dart:io';

class UsernameEmptyException implements Exception {
  // 自定义异常类
  String errMsg() => '用户名为空';
}

class UsernameLengthException implements Exception {
  int minLength;
  int maxLength;
  String message;

  // 自定义异常类
  UsernameLengthException(this.message, this.minLength, this.maxLength);

  String errMsg() =>
      '${this.message}, minLength:${this.minLength}, maxLength:${this.maxLength}';
}

String inputUsername() {
  print("请输入用户名:");
  // 读取键盘输入
  var name = stdin.readLineSync();

  if (null == name || name.length < 1) {
    throw UsernameEmptyException(); // 抛出异常
  }

  if (name.length > 16 || name.length < 8) {
    throw UsernameLengthException("用户名格式错误", 8, 16); // 抛出异常
  }

  return name;
}

void main() {
  try {
    var password = inputUsername();
    print("输入的用户名:$password");
  } on UsernameEmptyException {
    print("用户名不能为空");
  } on UsernameLengthException catch (e) {
    print("${e.errMsg()}");
  } catch (e) {
    print(e);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47