深色模式
Dart 控制流
循环
本页面展示了如何使用循环和辅助语句来控制 Dart 代码的流程:
for循环while和do while循环break和continue
你还可以使用以下方式在 Dart 中操控控制流:
- 分支语句,如
if和switch - 异常处理,如
try、catch和throw
for 循环
你可以使用标准的 for 循环进行迭代。例如:
dart
var message = StringBuffer('Dart is fun');
for (var i = 0; i < 5; i++) {
message.write('!');
}Dart 的 for 循环中的闭包会捕获索引的值。这避免了 JavaScript 中常见的一个陷阱。例如,考虑以下代码:
dart
var callbacks = [];
for (var i = 0; i < 2; i++) {
callbacks.add(() => print(i));
}
for (final c in callbacks) {
c();
}输出如预期所示,先是 0,然后是 1。相比之下,同样的例子在 JavaScript 中会输出两个 2。
有时,在遍历 Iterable 类型(如 List 或 Set)时,你可能不需要知道当前的迭代计数器。在这种情况下,使用 for-in 循环可以让代码更简洁:
dart
for (final candidate in candidates) {
candidate.interview();
}要处理从可迭代对象中获取的值,你还可以在 for-in 循环中使用模式匹配:
dart
for (final Candidate(:name, :yearsExperience) in candidates) {
print('$name has $yearsExperience of experience.');
}提示: 要练习使用 for-in 循环,请参考“可迭代集合教程”。
可迭代类还有一个 forEach() 方法可供选择:
dart
var collection = [1, 2, 3];
collection.forEach(print); // 1 2 3while 和 do-while 循环
while 循环在循环执行前计算条件:
dart
while (!isDone()) {
doSomething();
}do-while 循环在循环执行后计算条件:
dart
do {
printLine();
} while (!atEndOfPage());break 和 continue
使用 break 来停止循环:
dart
while (true) {
if (shutDownRequested()) break;
processIncomingRequests();
}使用 continue 跳过当前循环迭代,进入下一次迭代:
dart
for (int i = 0; i < candidates.length; i++) {
var candidate = candidates[i];
if (candidate.yearsExperience < 5) {
continue;
}
candidate.interview();
}如果你使用的是 List 或 Set 等可迭代对象,上述示例的写法可能会有所不同:
dart
candidates
.where((c) => c.yearsExperience >= 5)
.forEach((c) => c.interview());分支
本页面展示了如何使用分支语句来控制 Dart 代码的流程:
if语句和if表达式if-case语句和if-case表达式switch语句和switch表达式
你还可以使用以下方式在 Dart 中操控控制流:
- 循环语句,如
for和while - 异常处理,如
try、catch和throw
if 语句
Dart 支持带有可选 else 子句的 if 语句。if 后面括号内的条件必须是一个计算结果为布尔值的表达式:
dart
if (isRaining()) {
you.bringRainCoat();
} else if (isSnowing()) {
you.wearJacket();
} else {
car.putTopDown();
}若要了解如何在表达式上下文中使用 if,请查阅“条件表达式”。
if-case 语句
Dart 的 if 语句支持后跟模式的 case 子句:
dart
if (pair case [int x, int y]) return Point(x, y);如果模式与值匹配,那么该分支将执行,并且模式中定义的任何变量都将处于作用域内。
在前面的示例中,列表模式 [int x, int y] 与值 pair 匹配,因此分支 return Point(x, y) 会使用模式定义的变量 x 和 y 来执行。
否则,如果有 else 分支,控制流将进入该分支执行:
dart
if (pair case [int x, int y]) {
print('Was coordinate array $x,$y');
} else {
throw FormatException('Invalid coordinates.');
}if-case 语句提供了一种针对单个模式进行匹配和解构的方式。若要针对多个模式测试一个值,请使用 switch。
版本说明
if 语句中的 case 子句需要至少 3.0 版本的语言支持。
switch 语句
switch 语句会将一个值表达式与一系列 case 进行比较。每个 case 子句都是一个用于匹配该值的模式。你可以为 case 使用任何类型的模式。
当值与某个 case 的模式匹配时,该 case 的主体将执行。非空的 case 子句在执行完毕后会跳转到 switch 语句的末尾,它们不需要 break 语句。结束非空 case 子句的其他有效方式还有 continue、throw 或 return 语句。
当没有 case 子句匹配时,可以使用 default 或通配符 _ 子句来执行代码:
dart
var command = 'OPEN';
switch (command) {
case 'CLOSED':
executeClosed();
case 'PENDING':
executePending();
case 'APPROVED':
executeApproved();
case 'DENIED':
executeDenied();
case 'OPEN':
executeOpen();
default:
executeUnknown();
}空的 case 会落入下一个 case,从而允许多个 case 共享一个主体。对于不想落入下一个 case 的空 case,可以使用 break 作为其主体。对于非顺序的落入操作,你可以使用 continue 语句和标签:
dart
switch (command) {
case 'OPEN':
executeOpen();
continue newCase; // 继续在 newCase 标签处执行。
case 'DENIED': // 空的 case 会落入下一个 case。
case 'CLOSED':
executeClosed(); // 对于 DENIED 和 CLOSED 都会执行。
newCase:
case 'PENDING':
executeNowClosed(); // 对于 OPEN 和 PENDING 都会执行。
}你可以使用逻辑或模式来让多个 case 共享一个主体或一个守卫条件。若要了解更多关于模式和 case 子句的信息,请查阅“switch 语句和表达式”的模式文档。
switch 表达式
switch 表达式会根据匹配的 case 的表达式主体产生一个值。在 Dart 允许使用表达式的任何地方,你都可以使用 switch 表达式,但不能在表达式语句的开头使用。例如:
dart
var x = switch (y) { ... };
print(switch (x) { ... });
return switch (x) { ... };如果你想在表达式语句的开头使用 switch,请使用 switch 语句。
switch 表达式允许你将如下的 switch 语句:
dart
// 这里 slash、star、comma、semicolon 等是常量变量...
switch (charCode) {
case slash || star || plus || minus: // 逻辑或模式
token = operator(charCode);
case comma || semicolon: // 逻辑或模式
token = punctuation(charCode);
case >= digit0 && <= digit9: // 关系和逻辑与模式
token = number();
default:
throw FormatException('Invalid');
}重写为如下的表达式:
dart
token = switch (charCode) {
slash || star || plus || minus => operator(charCode),
comma || semicolon => punctuation(charCode),
>= digit0 && <= digit9 => number(),
_ => throw FormatException('Invalid'),
};switch 表达式的语法与 switch 语句的语法不同:
case不以case关键字开头。case的主体是一个单一的表达式,而不是一系列语句。- 每个
case都必须有一个主体;空的case没有隐式的落入操作。 case模式与其主体之间使用=>分隔,而不是:。case之间用,分隔(允许有一个可选的尾随,)。- 默认
case只能使用_,而不是既允许default又允许_。
版本说明
switch 表达式需要至少 3.0 版本的语言支持。
穷举性检查
穷举性检查是一项功能,如果一个值可能进入 switch 但不匹配任何 case,它会报告一个编译时错误。
dart
// 对 bool? 类型的非穷举 switch,缺少匹配 null 可能性的 case:
switch (nullableBool) {
case true:
print('yes');
case false:
print('no');
}默认 case(default 或 _)涵盖了可以通过 switch 的所有可能值,这使得对任何类型的 switch 都是穷举的。
枚举和密封类型在 switch 语句中特别有用,因为即使没有默认 case,它们的可能值也是已知且可完全枚举的。在类上使用 sealed 修饰符可以在对该类的子类型进行 switch 操作时启用穷举性检查:
dart
sealed class Shape {}
class Square implements Shape {
final double length;
Square(this.length);
}
class Circle implements Shape {
final double radius;
Circle(this.radius);
}
double calculateArea(Shape shape) => switch (shape) {
Square(length: var l) => l * l,
Circle(radius: var r) => math.pi * r * r,
};如果有人添加了 Shape 的新子类,这个 switch 表达式就会不完整。穷举性检查会通知你缺少的子类型。这让你可以以一种类似函数式代数数据类型的风格使用 Dart。
守卫子句
若要在 case 子句后设置一个可选的守卫子句,可以使用 when 关键字。守卫子句可以跟在 if case 以及 switch 语句和表达式后面。
dart
// switch 语句:
switch (something) {
case somePattern when some || boolean || expression:
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 守卫子句。
body;
}
// switch 表达式:
var value = switch (something) {
somePattern when some || boolean || expression => body,
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 守卫子句。
}
// if-case 语句:
if (something case somePattern when some || boolean || expression) {
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 守卫子句。
body;
}守卫子句会在匹配后计算一个任意的布尔表达式,这允许你对 case 主体是否应该执行添加更多约束。当守卫子句计算结果为 false 时,执行会进入下一个 case,而不是退出整个 switch。
错误处理
异常处理
Dart 代码可以抛出和捕获异常。异常是表示发生了意外情况的错误。如果异常未被捕获,抛出异常的隔离区(isolate)会被挂起,通常该隔离区及其所在的程序会终止运行。
与 Java 不同,Dart 的所有异常都是非检查型异常。方法不会声明它们可能抛出哪些异常,你也不必捕获任何异常。
Dart 提供了 Exception 和 Error 类型,以及许多预定义的子类型。当然,你也可以定义自己的异常。不过,Dart 程序可以抛出任何非空对象作为异常,而不仅仅是 Exception 和 Error 对象。
抛出异常
以下是抛出异常的示例:
dart
throw FormatException('Expected at least 1 section');你也可以抛出任意对象:
dart
throw 'Out of llamas!';注意: 生产级别的代码通常抛出实现了 Error 或 Exception 的类型。
因为抛出异常是一个表达式,所以你可以在 => 语句以及任何允许使用表达式的地方抛出异常:
dart
void distanceTo(Point other) => throw UnimplementedError();捕获异常
捕获异常可以阻止异常继续传播(除非你重新抛出异常)。捕获异常让你有机会处理它:
dart
try {
breedMoreLlamas();
} on OutOfLlamasException {
buyMoreLlamas();
}要处理可能抛出多种类型异常的代码,你可以指定多个 catch 子句。第一个匹配抛出对象类型的 catch 子句会处理该异常。如果 catch 子句没有指定类型,那么该子句可以处理任何类型的抛出对象:
dart
try {
breedMoreLlamas();
} on OutOfLlamasException {
// 特定异常
buyMoreLlamas();
} on Exception catch (e) {
// 其他任何异常
print('Unknown exception: $e');
} catch (e) {
// 未指定类型,处理所有情况
print('Something really unknown: $e');
}如上述代码所示,你可以使用 on 或 catch,也可以两者都用。当你需要指定异常类型时使用 on,当你的异常处理程序需要异常对象时使用 catch。
你可以为 catch() 指定一个或两个参数。第一个参数是抛出的异常,第二个参数是堆栈跟踪信息(一个 StackTrace 对象)。
dart
try {
// ···
} on Exception catch (e) {
print('Exception details:\n $e');
} catch (e, s) {
print('Exception details:\n $e');
print('Stack trace:\n $s');
}若要部分处理异常,同时允许它继续传播,可以使用 rethrow 关键字。
dart
void misbehave() {
try {
dynamic foo = true;
print(foo++); // 运行时错误
} catch (e) {
print('misbehave() partially handled ${e.runtimeType}.');
rethrow; // 允许调用者看到异常。
}
}
void main() {
try {
misbehave();
} catch (e) {
print('main() finished handling ${e.runtimeType}.');
}
}finally 子句
为了确保某些代码无论是否抛出异常都会运行,可以使用 finally 子句。如果没有 catch 子句匹配该异常,异常会在 finally 子句运行后继续传播:
dart
try {
breedMoreLlamas();
} finally {
// 即使抛出异常,也要进行清理。
cleanLlamaStalls();
}finally 子句会在任何匹配的 catch 子句之后运行:
dart
try {
breedMoreLlamas();
} catch (e) {
print('Error: $e'); // 先处理异常。
} finally {
cleanLlamaStalls(); // 然后进行清理。
}要了解更多信息,请查阅核心库的异常文档。
断言
在开发过程中,可以使用 assert 语句(assert(<条件>, <可选消息>);),如果布尔条件为 false,则中断正常执行。
dart
// 确保变量有非空值。
assert(text != null);
// 确保值小于 100。
assert(number < 100);
// 确保这是一个 https URL。
assert(urlString.startsWith('https'));要为断言添加消息,可以在 assert 中添加一个字符串作为第二个参数(可选地添加尾随逗号):
dart
assert(
urlString.startsWith('https'),
'URL ($urlString) should start with "https".',
);assert 的第一个参数可以是任何解析为布尔值的表达式。如果表达式的值为 true,断言成功,执行继续。如果为 false,断言失败,会抛出一个异常(AssertionError)。
断言究竟在什么时候起作用呢?这取决于你使用的工具和框架:
- Flutter 在调试模式下启用断言。
- 仅用于开发的工具(如
webdev serve)通常默认启用断言。 - 一些工具(如
dart run和dart compile js)通过命令行标志--enable-asserts支持断言。
在生产代码中,断言会被忽略,assert 的参数也不会被计算。
