# Dart教程 - 11 异常
# 11.1 异常的概念
什么是异常?
异常(Exceptions)是指在程序执行过程中出现的错误或异常情况,导致程序无法继续正常执行的事件。
Dart 提供了异常处理机制来捕获和处理这些异常。
例如:整数除数是0会抛出异常。
void main() {
int i = 5 ~/ 0;
print(i);
}
2
3
4
执行的时候就会发生错误:
当程序出现错误的时候,我们通常称之为:抛出异常。
程序抛出异常就无法继续执行了,但是任何程序都不可能是完美的没有bug的,只能尽可能的对错误进行预防和处理。
# 11.2 捕获异常
对异常进行预防和提前处理,这种行为通常称之为异常捕获。
一个程序出现任何错误,就停止运行肯定不是我们希望看到的,即使程序出现错误,哪怕给与用户一个错误提示,也是一种积极的处理方式。
# 1 异常捕获语法
最简单的异常捕获语法:
try {
// 可能会抛出异常的代码
} catch (e) {
// 捕获并处理异常
}
2
3
4
5
举个栗子:
import 'dart:convert';
void main() {
try {
String jsonString = 'Hello';
// 将字符串转换为字典
Map<String, dynamic> data = jsonDecode(jsonString);
print("无法被指定到的代码");
} catch (e) {
// 捕获并处理异常
print("JSON字符串格式不正确");
}
}
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');
}
}
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");
}
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字符串格式不正确");
}
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字符串格式不正确");
}
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");
}
}
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("出现异常也会被执行");
}
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字符串格式不正确");
}
}
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("用户名格式错误,请重新输入");
}
}
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);
}
}
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