集合
Dart 内置支持列表(list)、集合(set)和映射(map)类型的集合。如需了解更多关于配置集合包含类型的信息,请查阅 泛型。
列表(Lists)
几乎所有编程语言中最常见的集合类型是数组,即有序的对象组。在 Dart 中,数组是 List 对象,因此大多数人直接称其为列表。
Dart 列表字面量由方括号 [] 包裹的逗号分隔元素列表表示,每个元素通常是一个表达式。以下是一个简单的 Dart 列表:
var list = [1, 2, 3];注意
Dart 会推断 list 的类型为 List<int>。如果尝试向此列表添加非整数对象,分析器或运行时会抛出错误。更多信息请阅读 类型推断。
可以在 Dart 集合字面量的最后一个元素后添加逗号,该尾随逗号不会影响集合,但有助于避免复制粘贴错误。
var list = ['Car', 'Boat', 'Plane'];列表使用基于零的索引,其中 0 是第一个元素的索引,list.length - 1 是最后一个元素的索引。可以使用 .length 属性获取列表长度,并使用下标运算符 [] 访问列表元素:
var list = [1, 2, 3];
assert(list.length == 3);
assert(list[1] == 2);
list[1] = 1;
assert(list[1] == 1);若要创建编译时常量列表,需在列表字面量前添加 const:
var constantList = const [1, 2, 3];
// constantList[1] = 1; // 此行会导致错误更多关于列表的信息,请参考 dart:core 文档的列表部分。
集合(Sets)
Dart 中的集合是无序的唯一元素集合。Dart 对集合的支持由集合字面量和 Set 类型提供。
以下是使用集合字面量创建的简单 Dart 集合:
var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};注意
Dart 会推断 halogens 的类型为 Set<String>。如果尝试向集合添加错误类型的元素,分析器或运行时会抛出错误。更多信息请阅读 类型推断。
若要创建空集合,需使用带类型参数的 {},或将 {} 赋值给 Set 类型的变量:
var names = <String>{};
// Set<String> names = {}; // 这也有效
// var names = {}; // 创建的是 Map,而非 Set集合还是映射?
映射字面量的语法与集合字面量类似。由于映射字面量先出现,{} 默认属于 Map 类型。如果忘记为 {} 或其赋值的变量添加类型注解,Dart 会创建 Map<dynamic, dynamic> 类型的对象。
使用 add() 或 addAll() 方法向现有集合添加元素:
var elements = <String>{};
elements.add('fluorine');
elements.addAll(halogens);使用 .length 获取集合中的元素数量:
var elements = <String>{};
elements.add('fluorine');
elements.addAll(halogens);
assert(elements.length == 5);若要创建编译时常量集合,需在集合字面量前添加 const:
final constantSet = const {
'fluorine',
'chlorine',
'bromine',
'iodine',
'astatine',
};
// constantSet.add('helium'); // 此行会导致错误更多关于集合的信息,请参考 dart:core 文档的集合部分。
映射(Maps)
在映射中,每个元素是一个键值对。键值对中的每个键与一个值相关联,键和值可以是任意类型的对象。每个键只能出现一次,但同一个值可以与多个不同的键相关联。Dart 对映射的支持由映射字面量和 Map 类型提供。
以下是使用映射字面量创建的简单 Dart 映射:
var gifts = {
// 键: 值
'first': 'partridge',
'second': 'turtledoves',
'fifth': 'golden rings',
};
var nobleGases = {2: 'helium', 10: 'neon', 18: 'argon'};注意
Dart 会推断 gifts 的类型为 Map<String, String>,nobleGases 的类型为 Map<int, String>。如果尝试向任一映射添加错误类型的值,分析器或运行时会抛出错误。更多信息请阅读 类型推断。
也可以使用 Map 构造函数创建相同的对象:
var gifts = Map<String, String>();
gifts['first'] = 'partridge';
gifts['second'] = 'turtledoves';
gifts['fifth'] = 'golden rings';
var nobleGases = Map<int, String>();
nobleGases[2] = 'helium';
nobleGases[10] = 'neon';
nobleGases[18] = 'argon';注意
如果您来自 C# 或 Java 等语言,可能期望看到 new Map() 而非 Map()。在 Dart 中,new 关键字是可选的。详情请见 使用构造函数。
使用下标赋值运算符 []= 向现有映射添加新的键值对:
var gifts = {'first': 'partridge'};
gifts['fourth'] = 'calling birds'; // 添加键值对使用下标运算符 [] 从映射中检索值:
var gifts = {'first': 'partridge'};
assert(gifts['first'] == 'partridge');如果查找映射中不存在的键,将返回 null:
var gifts = {'first': 'partridge'};
assert(gifts['fifth'] == null);使用 .length 获取映射中的键值对数量:
var gifts = {'first': 'partridge'};
gifts['fourth'] = 'calling birds';
assert(gifts.length == 2);若要创建编译时常量映射,需在映射字面量前添加 const:
final constantMap = const {2: 'helium', 10: 'neon', 18: 'argon'};
// constantMap[2] = 'Helium'; // 此行会导致错误更多关于映射的信息,请参考 dart:core 文档的映射部分。
集合元素(Collection elements)
集合字面量包含一系列元素。在运行时,每个元素会被求值,生成零个或多个值,然后插入到结果集合中。这些元素主要分为两类:叶子元素(leaf element)和控制流元素(control flow element)。
叶子元素:向集合字面量中插入单个项。
- 表达式元素:计算单个表达式并将结果值插入集合。
- 映射条目元素:计算键和值表达式对,并将结果条目插入集合。
控制流元素:有条件或迭代地向周围集合添加零个或多个值。
- 空安全元素:计算表达式,若结果不为
null,则将值插入周围集合。 - 展开元素:遍历给定序列(集合表达式),并将所有结果值插入周围集合。
- 空安全展开元素:与展开元素类似,但允许集合为
null,若为null则不插入任何内容。 if元素:根据给定的条件表达式有条件地计算内部元素,若条件为假,可选地计算else元素。for元素:迭代并重复计算给定的内部元素,插入零个或多个结果值。
- 空安全元素:计算表达式,若结果不为
表达式元素(Expression elements)
表达式元素计算单个表达式并将结果值插入集合。该表达式可以包含字面量、变量、运算符、函数调用和构造函数调用等各种构造。
集合中的表达式元素语法如下:
<expression>映射条目元素(Map entry elements)
映射条目元素计算键和值表达式对,并将结果条目插入集合。键和值均可以是表达式。
集合中的映射条目元素语法如下:
<key_expression>: <value_expression>空安全元素(Null-aware elements)
空安全元素计算表达式,若结果不为 null,则将值插入周围集合。
版本说明
空安全集合元素需要语言版本至少为 3.8。
表达式元素中的空安全元素语法:
?<expression>映射条目元素中的空安全元素语法:
// 键为空安全元素
?<key_expression>: <value_expression>
// 值为空安全元素
<key_expression>: ?<value_expression>
// 键和值均为空安全元素
?<key_expression>: ?<value_expression>在以下示例中,空安全元素 ?absentValue 的结果未添加到列表 items 中,因为 absentValue 为 null:
int? absentValue = null;
int? presentValue = 3;
var items = [
1,
?absentValue,
?presentValue,
absentValue, // 直接插入 null
5,
]; // [1, 3, null, 5]以下示例展示了在映射条目元素中使用空安全元素的多种方式:
String? presentKey = 'Apple';
String? absentKey = null;
int? presentValue = 3;
int? absentValue = null;
var itemsA = {presentKey: absentValue}; // {Apple: null}
var itemsB = {presentKey: ?absentValue}; // {}(值为 null,不插入)
var itemsC = {absentKey: presentValue}; // {null: 3}(键为 null,允许插入)
var itemsD = {?absentKey: presentValue}; // {}(键为 null,空安全元素不插入)
var itemsE = {absentKey: absentValue}; // {null: null}
var itemsF = {?absentKey: ?absentValue}; // {}(键和值均为 null,不插入)展开元素(Spread elements)
展开元素遍历给定序列,并将所有结果值插入周围集合。
集合中的展开元素语法如下,sequence_expression 可以是计算结果为实现 Iterable 的对象的任意表达式:
...<sequence_expression>在以下示例中,列表 a 中的元素被添加到列表 items 中:
var a = [1, 2, null, 4];
var items = [0, ...a, 5]; // [0, 1, 2, null, 4, 5]如果展开的表达式可能为 null,且希望忽略 null(不插入任何元素),请使用空安全展开元素。
更多关于展开运算符的信息,请查阅 展开运算符。
空安全展开元素(Null-aware spread elements)
空安全展开元素与展开元素类似,但允许集合为 null,若为 null 则不插入任何内容。
集合中的空安全展开元素语法如下:
...?<sequence_expression>在以下示例中,列表 a 因 null 被忽略,而列表 b 中的元素被添加到 items 中。请注意,若集合本身不为 null 但包含 null 元素,这些 null 元素仍会被添加到结果中:
List<int>? a = null;
var b = [1, null, 3];
var items = [0, ...?a, ...?b, 4]; // [0, 1, null, 3, 4]由于空安全特性,不能对可能为 null 的值执行展开操作(...)。以下示例会产生编译时错误,因为 extraOptions 参数可为 null,且对其使用的展开运算符非空安全:
✗ static analysis: failure
List<String> buildCommandLine(
String executable,
List<String> options, [
List<String>? extraOptions,
]) {
return [
executable,
...options,
...extraOptions, // <-- 错误:不能对 nullable 集合使用非空安全展开
];
}
// 用法:
// buildCommandLine('dart', ['run', 'my_script.dart'], null);
// 结果:
// 编译时错误若要展开可为 null 的集合,请使用空安全展开元素。以下示例有效,因为对 extraOptions 使用了空安全展开运算符:
List<String> buildCommandLine(
String executable,
List<String> options, [
List<String>? extraOptions,
]) {
return [
executable,
...options,
...?extraOptions, // <-- 现在正确
];
}
// 用法:
// buildCommandLine('dart', ['run', 'my_script.dart'], null);
// 结果:
// [dart, run, my_script.dart]更多关于空安全展开运算符的信息,请查阅 展开运算符。
if 元素(If elements)
if 元素根据给定的条件表达式有条件地计算内部元素,若条件为假,可选地计算 else 元素。
if 元素有多种语法变体:
// 若布尔表达式为 true,包含结果
if (<bool_expression>) <result>
// 若表达式匹配模式,包含结果
if (<expression> case <pattern>) <result>
// 若操作结果为 true,包含第一个结果,否则包含第二个结果
if (<bool_expression>) <result> else <result>
// 若表达式匹配模式,包含第一个结果,否则包含第二个结果
if (<expression> case <pattern>) <result> else <result>以下示例展示了在集合中使用带布尔表达式的 if 元素的多种方式:
var includeItem = true;
var items = [0, if (includeItem) 1, 2, 3]; // [0, 1, 2, 3]
var includeItem = true;
var items = [0, if (!includeItem) 1, 2, 3]; // [0, 2, 3]
var name = 'apple';
var items = [0, if (name == 'orange') 1 else 10, 2, 3]; // [0, 10, 2, 3]
var name = 'apple';
var items = [
0,
if (name == 'kiwi') 1 else if (name == 'pear') 10,
2,
3,
]; // [0, 2, 3]以下示例展示了在集合中使用带 case 部分的 if 元素的多种方式:
Object data = 123;
var typeInfo = [
if (data case int i) 'Data is an integer: $i',
if (data case String s) 'Data is a string: $s',
if (data case bool b) 'Data is a boolean: $b',
if (data case double d) 'Data is a double: $d',
]; // [Data is an integer: 123, Data is a double: 123](int 可匹配 double 模式)
var word = 'hello';
var items = [
1,
if (word case String(length: var wordLength)) wordLength, // 匹配字符串长度
3,
]; // [1, 5, 3]
var orderDetails = ['Apples', 12, ''];
var summary = [
'Product: ${orderDetails[0]}',
if (orderDetails case [_, int qty, _]) 'Quantity: $qty', // 模式匹配列表结构
if (orderDetails case [_, _, '']) // 第三个元素为空字符串
'Delivery: Not Started'
else
'Delivery: In Progress',
]; // [Product: Apples, Quantity: 12, Delivery: Not Started]可以混合使用不同的 if 操作和 else if 部分。例如:
var a = 'apple';
var b = 'orange';
var c = 'mango';
var items = [0,
if (a == 'apple') 1 else if (a case 'mango') 10, // 匹配 'apple',结果为 1
if (b case 'pear') 2 else if (b == 'mango') 20, // 不匹配,结果为 null(不插入)
if (c case 'apple') 3 else if (c case 'mango') 30, // 匹配 'mango',结果为 30
4,
]; // [0, 1, 30, 4]更多关于 if 条件语句的信息,请查阅 if 语句。关于 if-case 条件语句的信息,请查阅 if-case 语句。
for 元素(For elements)
for 元素通过迭代重复计算给定的内部元素,并插入零个或多个结果值。
集合中的 for 元素语法如下:
for (<expression> in <collection>) <result> // 遍历集合
for (<initialization_clause>; <condition_clause>; <increment_clause>) <result> // 传统 for 循环语法以下示例展示了在集合中使用 for 元素的多种方式:
var numbers = [2, 3, 4];
var items = [1, for (var n in numbers) n * n, 7]; // [1, 4, 9, 16, 7](遍历数组并计算平方)
var items = [1, for (var x = 5; x > 2; x--) x, 7]; // [1, 5, 4, 3, 7](传统 for 循环递减)
var items = [1, for (var x = 2; x < 4; x++) x, 7]; // [1, 2, 3, 7](传统 for 循环递增)更多关于 for 循环的信息,请查阅 for 循环。
嵌套控制流元素(Nest control flow elements)
可以在控制流元素内部嵌套其他控制流元素,这是其他语言中列表推导式的强大替代方案。
在以下示例中,仅将 numbers 中的偶数包含在 items 中:
var numbers = [1, 2, 3, 4, 5, 6, 7];
var items = [
0,
for (var n in numbers) // 遍历数组
if (n.isEven) n, // 条件过滤偶数
8,
]; // [0, 2, 4, 6, 8]在 if 或 for 元素内部直接对集合字面量使用展开操作是常见且惯用的写法。例如:
var items = [
if (condition) oneThing(), // 条件插入单个元素
if (condition) ...[multiple(), things()], // 条件展开多个元素
]; // 结果:[oneThing, multiple_a, multiple_b, things](假设 condition 为 true)可以任意深度嵌套各种元素。在以下示例中,if、for 和展开元素在集合中相互嵌套:
var nestItems = true;
var ys = [1, 2, 3, 4];
var items = [
if (nestItems) ...[ // 条件展开内部集合
for (var x = 0; x < 3; x++) // 外层循环
for (var y in ys) // 内层循环
if (x < y) x + y * 10, // 条件计算
],
];
// 计算过程:
// x=0 时,y=1→10, y=2→20, y=3→30, y=4→40(均满足 x<y)
// x=1 时,y=2→21, y=3→31, y=4→41(y>1)
// x=2 时,y=3→32, y=4→42(y>2)
// 结果:[10, 20, 30, 40, 21, 31, 41, 32, 42]