Dart 3.0 引入的模式匹配(Pattern Matching) 是一套强大的语法特性,让你能够按形状解构数据,并根据值的结构或内容执行不同逻辑。它不只是 switch 的升级,而是贯穿变量声明、赋值、控制流的统一解构与匹配机制。
下面从变量声明与解构开始,逐步深入到 switch、if-case 等场景,全面展示模式匹配的用法。
一、核心概念
- 模式(Pattern) :描述对数据结构的期望形状,例如“是一个大于 0 的整数”、“是一个有两个元素的列表”、“是一个带 name 字段的对象”等。
- 匹配(Matching) :检查一个值是否符合模式,若符合,通常还会将值中的子部分解构并绑定到变量。
- 解构(Destructuring) :将复合值(列表、Map、对象、记录)拆开,直接提取内部值赋给变量。
二、变量声明与解构:模式匹配的基石
在变量声明或赋值语句中直接使用模式,可以一步完成“检查形状 + 提取数据” 。
2.1 常量模式与变量模式
dart
// 常量模式:直接匹配字面量(通常与 switch 联用)
// 变量模式:匹配任何值并绑定到新变量
var a = 123; // 普通变量声明,不是模式
final b = 'hi';
// 在解构语境中,变量模式才体现“模式”角色
2.2 列表解构
dart
// 解构固定长度列表
var [x, y] = [1, 2];
print(x); // 1
// 使用 ... 收集剩余元素
var [first, ...rest] = [10, 20, 30, 40];
print(rest); // [20, 30, 40]
// 忽略某些元素(通配符 _)
var [_, second, _] = ['a', 'b', 'c'];
print(second); // b
// 嵌套解构
var [int a, [int b, int c]] = [1, [2, 3]];
2.3 Map 解构
dart
// 按键提取,键必须是编译时常量
var {'name': String name, 'age': int age} = {'name': 'Alice', 'age': 30};
print(name); // Alice
// 可以只提取需要的键,并重命名变量
var {'id': id, 'score': final score} = {'id': 101, 'score': 95.5, 'grade': 'A'};
print(id); // 101
2.4 记录解构
Dart 3.0 同时引入了记录(Record),模式匹配天然支持解构记录。
dart
// 位置字段记录
var (a, b) = (1, 2);
print(a); // 1
// 命名字段记录
var (x: xVal, y: yVal) = (x: 42, y: 100);
print(xVal); // 42
// 混合
var (name, age: years) = ('Bob', age: 25);
2.5 对象解构
对象解构语法:var 对象(命名字段1 : 变量1 , 命名字段2 : 变量2, ...) = 对象。
get方法也能被解构
dart
class Point {
final int x, y;
Point(this.x, this.y);
int get sum => x+y;
}
final Point(x: px, y: py,sum: sum) = Point(3, 4);
print(px); // 3
print(sum); // 7
三、switch 与 if-case:基于模式的分支控制
模式匹配最亮眼的应用场景是增强的 switch,它现在可以作为表达式返回值,并且支持所有模式类型。
3.1 switch 表达式
dart
String describe(dynamic value) => switch (value) {
// 常量模式
0 => 'Zero',
// 关系模式
>= 1 && <= 10 => 'Small positive',
// 类型模式 + 变量绑定
String s => 'String of length ${s.length}',
// 列表模式 + 解构
[int a, int b] => 'List with two ints: $a, $b',
// 记录模式 + 解构
(x: var a, y: var b) => 'Record with x=$a, y=$b',
// 通配符(默认)
_ => 'Something else',
};
- 穷举检查:如果 switch 表达式的值类型是有限集(如枚举、密封类),编译器会要求覆盖所有可能情况,否则报错。
- 无需 break:每个 case 独立,不会贯穿。
3.2 switch 语句
传统的 switch 语句也获得了模式匹配能力,并且支持多条语句:
dart
switch (shape) {
case Circle(radius: var r) when r > 10:
print('Big circle');
case Circle(radius: var r):
print('Small circle');
case Square(size: var s):
print('Square of size $s');
}
when 子句可以添加额外条件。
3.3 if-case 语句
当你只需要检查一种特定模式时,if-case 更简洁:
dart
if (json case {'user': String name, 'permissions': ['read', ...]}) {
print('User $name has read permission');
}
这相当于“如果值匹配这个模式,则解构并执行”,是 switch 的单分支特化。
四、常用模式类型一览表
| 模式类型 | 语法示例 | 说明 | |
|---|---|---|---|
| 常量模式 | 123、'hello'、true、null | 匹配具体字面量 | |
| 变量模式 | var name、final age | 匹配任何值并绑定到新变量 | |
| 通配符模式 | _ | 匹配任何值,不绑定 | |
| 关系模式 | >= 0、== 42 | 用关系运算符匹配 | |
| 逻辑与/或 | >=0 && <=10、`'a' | 'b'` | 组合多个子模式 |
| 类型模式 | int x、String s | 匹配特定类型并绑定变量 | |
| 列表模式 | [a, b]、[first, ...rest] | 匹配列表结构并解构元素 | |
| Map 模式 | {'key': var v} | 匹配 Map 并提取特定键的值 | |
| 记录模式 | (a, b)、(x: var a, y: var b) | 匹配记录并按位置/字段名解构 | |
| 对象模式 | Point(x: var x, y: var y) | 匹配对象并调用 getter 解构 | |
| 括号模式 | (var x) | 用于控制优先级 |
五、模式匹配为何重要?
-
消除样板代码
不再需要写if (value is List) ... as List再手动索引取值,模式匹配将“类型检查 + 类型转换 + 解构”合成一步。 -
提升可读性与意图表达
代码直接反映数据的预期形状,逻辑扁平化。 -
编译时安全
- switch 表达式的穷举性强制处理所有分支。
- 配合密封类(sealed class) ,可以构建代数数据类型(ADT),模式匹配保证穷举。
-
统一解构语法
以前解构列表、Map、对象各有一套零散方法,现在全部用统一、声明式的模式语法完成。
六、综合实例:处理 JSON
dart
void parseJson(Map<String, dynamic> json) {
switch (json) {
case {'type': 'user', 'name': String name, 'age': int age}:
print('User $name is $age years old');
case {'type': 'product', 'id': int id, 'price': num price}:
print('Product $id costs $$price');
case {'type': 'order', 'items': List items, 'total': num total}:
print('Order has ${items.length} items, total $$total');
case {'type': 'unknown'}:
print('Unknown type');
default:
print('Invalid format');
}
}
七、注意事项
- 模式匹配在 Dart 3.0 默认启用,无需修改
analysis_options.yaml。 - 变量模式中可以使用
var或final,类型可省略(编译器自动推断)。 - 对象模式要求类有对应的 getter,且构造函数的参数名与 getter 名一致(可自定义,用
new前缀)。 switch表达式必须返回一个值,所有分支必须能计算出值,且类型一致。
总结
Dart 3.0 的模式匹配将解构与分支控制统一在简洁的语法之下,极大提升了处理嵌套数据结构的表达能力。从变量声明到 switch 表达式,它让代码更安全、更直观,是 Dart 语言向现代化、函数式方向迈出的关键一步。掌握模式匹配,就能用更少的代码处理更复杂的逻辑,同时获得编译器的强力支持。