深色模式
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 3
while
和 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
的参数也不会被计算。