Collections

Collections

Dart 对列表、集合和映射集合提供了内置支持。要了解更多关于配置集合所包含类型的信息,请查阅泛型

列表

也许在几乎所有编程语言中最常见的集合就是数组,即对象的有序组。在 Dart 中,数组是 List 对象,所以大多数人直接称它们为列表。

Dart 列表字面量由方括号 [] 包围的、逗号分隔的元素列表表示。每个元素通常是一个表达式。这是一个简单的 Dart 列表:

1
var list = [1, 2, 3];

注意
Dart 推断 list 的类型为 List<int>。如果你尝试向此列表添加非整数对象,分析器或运行时会引发错误。更多信息,请阅读类型推断

你可以在 Dart 集合字面量的最后一个元素后面添加一个逗号。这个尾随逗号不影响集合,但有助于防止复制粘贴错误。

1
var list = ['Car', 'Boat', 'Plane'];

列表使用从零开始的索引,其中 0 是第一个元素的索引,list.length - 1 是最后一个元素的索引。你可以使用 .length 属性获取列表的长度,并使用下标运算符 [] 访问列表的元素:

1
2
3
4
5
6
var list = [1, 2, 3];
assert(list.length == 3);
assert(list[1] == 2);

list[1] = 1;
assert(list[1] == 1);

要创建一个编译时常量的列表,请在列表字面量前添加 const

1
2
var constantList = const [1, 2, 3];
// constantList[1] = 1; // 这一行将导致错误。

有关列表的更多信息,请参阅 dart:core 文档的列表部分。

集合

Dart 中的集合是唯一元素的无序集合。Dart 通过集合字面量和 Set 类型来支持集合。

这是一个简单的 Dart 集合,使用集合字面量创建:

1
var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};

注意
Dart 推断 halogens 的类型为 Set<String>。如果你尝试向集合中添加错误类型的元素,分析器或运行时会引发错误。更多信息,请阅读类型推断

要创建一个空集合,请在 {} 前面使用类型参数,或将 {} 赋值给 Set 类型的变量:

1
2
3
var names = <String>{};
// Set<String> names = {}; // 这也有效。
// var names = {}; // 这会创建一个映射,而不是集合。

是集合还是映射?
映射字面量的语法与集合字面量的语法相似。因为映射字面量出现得更早,所以 {} 默认表示 Map 类型。如果你忘记了在 {} 或其赋值变量上添加类型注解,那么 Dart 会创建一个 Map<dynamic, dynamic> 类型的对象。

使用 add()addAll() 方法向现有集合添加项:

1
2
3
var elements = <String>{};
elements.add('fluorine');
elements.addAll(halogens);

使用 .length 获取集合中的项数:

1
2
3
4
var elements = <String>{};
elements.add('fluorine');
elements.addAll(halogens);
assert(elements.length == 5);

要创建一个编译时常量的集合,请在集合字面量前添加 const

1
2
3
4
5
6
7
8
final constantSet = const {
'fluorine',
'chlorine',
'bromine',
'iodine',
'astatine',
};
// constantSet.add('helium'); // 这一行将导致错误。

有关集合的更多信息,请参阅 dart:core 文档的集合部分。

映射

在映射中,每个元素都是一个键值对。每对中的键都与一个值相关联,键和值都可以是任何类型的对象。每个键只能出现一次,但同一个值可以与多个不同的键相关联。Dart 通过映射字面量和 Map 类型来支持映射。

这里有几个简单的 Dart 映射,使用映射字面量创建:

1
2
3
4
5
6
7
8
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 构造函数创建相同的对象:

1
2
3
4
5
6
7
8
9
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 关键字是可选的。有关详细信息,请参阅使用构造函数

使用下标赋值运算符 []= 向现有映射添加新的键值对:

1
2
var gifts = {'first': 'partridge'};
gifts['fourth'] = 'calling birds'; // 添加一个键值对

使用下标运算符 [] 从映射中检索值:

1
2
var gifts = {'first': 'partridge'};
assert(gifts['first'] == 'partridge');

如果你查找的键不在映射中,你会得到 null

1
2
var gifts = {'first': 'partridge'};
assert(gifts['fifth'] == null);

使用 .length 获取映射中键值对的数量:

1
2
3
var gifts = {'first': 'partridge'};
gifts['fourth'] = 'calling birds';
assert(gifts.length == 2);

要创建一个编译时常量的映射,请在映射字面量前添加 const

1
2
3
final constantMap = const {2: 'helium', 10: 'neon', 18: 'argon'};

// constantMap[2] = 'Helium'; // 这一行将导致错误。

有关映射的更多信息,请参阅 dart:core 文档的映射部分。

集合元素

集合字面量包含一系列元素。在运行时,每个元素都会被求值,产生零个或多个值,然后插入到结果集合中。这些元素分为两大类:叶元素和控制流元素。

  • 叶元素:将单个项插入到集合字面量中。

    • 表达式元素:求值单个表达式,并将结果值插入集合。
    • 映射条目元素:求值一对键和值表达式,并将结果条目插入集合。
  • 控制流元素:有条件地或迭代地向周围的集合中添加零个或多个值。

    • 空感知元素:求值一个表达式,如果结果不为 null,则将该值插入到周围的集合中。
    • 展开元素:遍历给定的序列(集合表达式),并将所有结果值插入到周围的集合中。
    • 空感知展开元素:与展开元素类似,但允许集合为 null,如果为 null 则不插入任何内容。
    • if 元素:根据给定的条件表达式有条件地求值内部元素,并且如果条件为假,可以选择性地求值另一个 else 元素。
    • for 元素:迭代并重复求值给定的内部元素,插入零个或多个结果值。

要了解更多关于集合元素的信息,请参阅以下部分。

表达式元素

表达式元素求值单个表达式,并将结果值插入集合。这个表达式可以包含各种结构,如字面量、变量、运算符、函数调用和构造函数调用。

表达式元素在集合中的语法如下:

1
<expression>

映射条目元素

映射条目元素求值一对键和值表达式,并将结果条目插入集合。这对中的键和值都可以是表达式。

映射条目元素在集合中的语法如下:

1
<key_expression>: <value_expression>

空感知元素

空感知元素求值一个表达式,如果结果不为 null,则将该值插入到周围的集合中。

版本说明
空感知集合元素需要语言版本至少为 3.8。

空感知元素在表达式元素中的语法如下:

1
?<expression>

空感知元素在映射条目元素中的语法如下:

1
2
// 键是空感知元素
?<key_expression>: <value_expression>
1
2
// 值是空感知元素
<key_expression>: ?<value_expression>
1
2
// 键和值都是空感知元素
?<key_expression>: ?<value_expression>

在下面的例子中,空感知元素 ?a 的结果没有被添加到名为 items 的列表中,因为 anull

1
2
3
4
5
6
7
8
9
int? absentValue = null;
int? presentValue = 3;
var items = [
1,
?absentValue,
?presentValue,
absentValue,
5,
]; // [1, 3, null, 5]

下面的例子说明了在映射条目元素中可以使用空感知元素的各种方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
String? presentKey = 'Apple';
String? absentKey = null;

int? presentValue = 3;
int? absentValue = null;

var itemsA = {presentKey: absentValue}; // {Apple: null}
var itemsB = {presentKey: ?absentValue}; // {}

var itemsC = {absentKey: presentValue}; // {null: 3}
var itemsD = {?absentKey: presentValue}; // {}

var itemsE = {absentKey: absentValue}; // {null: null}
var itemsF = {?absentKey: ?absentValue}; // {}

展开元素

展开元素遍历给定的序列,并将所有结果值插入到周围的集合中。

展开元素在集合中的语法如下。序列表达式可以是任何求值为实现了 Iterable 对象的表达式:

1
...<sequence_expression>

在下面的例子中,名为 a 的列表中的元素被添加到了名为 items 的列表中。

1
2
var a = [1, 2, null, 4];
var items = [0, ...a, 5]; // [0, 1, 2, null, 4, 5]

如果你展开的表达式可能求值为 null,并且你希望忽略 null(并且不插入任何元素),请使用空感知展开元素。

要了解更多关于展开运算符的信息,请参阅展开运算符

空感知展开元素

空感知展开元素与展开元素类似,但它允许集合为 null,如果为 null 则不插入任何内容。

空感知展开元素在集合中的语法如下:

1
...?<sequence_expression>

在下面的例子中,名为 a 的列表被忽略了,因为它是 null,但名为 b 的列表中的元素被添加到了名为 items 的列表中。请注意,如果集合本身不为 null,但它包含 null 元素,那么这些 null 元素仍然会被添加到结果中。

1
2
3
List<int>? a = null;
var b = [1, null, 3];
var items = [0, ...?a, ...?b, 4]; // [0, 1, null, 3, 4]

由于空安全,你不能对可能为 null 的值执行展开操作 (...)。下面的例子会产生编译时错误,因为 extraOptions 参数是可空的,并且用在 extraOptions 上的展开运算符不是空感知的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static analysis: failure
List<String> buildCommandLine(
String executable,
List<String> options, [
List<String>? extraOptions,
]) {
return [
executable,
...options,
...extraOptions, // <-- 错误
];
}

// 用法:
// buildCommandLine('dart', ['run', 'my_script.dart'], null);
// 结果:
// 编译时错误

如果你想展开一个可空的集合,请使用空感知展开元素。下面的例子是有效的,因为在 extraOptions 上使用了空感知展开运算符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
List<String> buildCommandLine(
String executable,
List<String> options, [
List<String>? extraOptions,
]) {
return [
executable,
...options,
...?extraOptions, // <-- 现在 OK 了。
];
}

// 用法:
// buildCommandLine('dart', ['run', 'my_script.dart'], null);
// 结果:
// [dart, run, my_script.dart]

要了解更多关于空感知展开运算符的信息,请参阅展开运算符

if 元素

if 元素根据给定的条件表达式有条件地求值内部元素,并且如果条件为假,可以选择性地求值另一个 else 元素。

if 元素有几种语法变体:

1
2
// 如果布尔表达式为真,则包含结果。
if (<bool_expression>) <result>
1
2
// 如果表达式匹配模式,则包含结果。
if (<expression> case <pattern>) <result>
1
2
// 如果操作解析为真,则包含第一个结果,否则包含第二个结果。
if (<bool_expression>) <result> else <result>
1
2
// 如果操作解析为真,则包含第一个结果,否则包含第二个结果。
if (<expression> case <pattern>) <result> else <result>

下面的例子说明了在集合中使用带有布尔表达式的 if 元素的各种方式:

1
2
var includeItem = true;
var items = [0, if (includeItem) 1, 2, 3]; // [0, 1, 2, 3]
1
2
var includeItem = true;
var items = [0, if (!includeItem) 1, 2, 3]; // [0, 2, 3]
1
2
var name = 'apple';
var items = [0, if (name == 'orange') 1 else 10, 2, 3]; // [0, 10, 2, 3]
1
2
3
4
5
6
7
var name = 'apple';
var items = [
0,
if (name == 'kiwi') 1 else if (name == 'pear') 10,
2,
3,
]; // [0, 2, 3]

下面的例子说明了在集合中使用带有 case 部分的 if 元素的各种方式:

1
2
3
4
5
6
7
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]
1
2
3
4
5
6
var word = 'hello';
var items = [
1,
if (word case String(length: var wordLength)) wordLength,
3,
]; // [1, 5, 3]
1
2
3
4
5
6
7
8
9
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 部分混合使用。例如:

1
2
3
4
5
6
7
8
9
10
var a = 'apple';
var b = 'orange';
var c = 'mango';
var items = [
0,
if (a == 'apple') 1 else if (a case 'mango') 10,
if (b case 'pear') 2 else if (b == 'mango') 20,
if (c case 'apple') 3 else if (c case 'mango') 30,
4,
]; // [0, 1, 30, 4]

要了解更多关于 if 条件语句的信息,请参阅 if 语句。要了解更多关于 if-case 条件语句的信息,请参阅 if-case 语句

for 元素

for 元素迭代并重复求值给定的内部元素,插入零个或多个结果值。

for 元素在集合中的语法如下:

1
for (<expression> in <collection>) <result>
1
for (<initialization_clause>; <condition_clause>; <increment_clause>) <result>

下面的例子说明了在集合中使用 for 元素的各种方式:

1
2
var numbers = [2, 3, 4];
var items = [1, for (var n in numbers) n * n, 7]; // [1, 4, 9, 16, 7]
1
var items = [1, for (var x = 5; x > 2; x--) x, 7]; // [1, 5, 4, 3, 7]
1
var items = [1, for (var x = 2; x < 4; x++) x, 7]; // [1, 2, 3, 7]

要了解更多关于 for 循环的信息,请参阅 for 循环

嵌套控制流元素

你可以将控制流元素相互嵌套。这是其他语言中列表推导式的一个强大替代方案。

在下面的例子中,只有 numbers 中的偶数被包含在 items 中。

1
2
3
4
5
6
7
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]

iffor 元素内部立即对集合字面量使用展开是常见且地道的用法。例如:

1
2
3
4
var items = [
if (condition) oneThing(),
if (condition) ...[multiple(), things()],
]; // [oneThing, multiple_a, multiple_b, things]

你可以任意深度地嵌套各种元素。在下面的例子中,iffor 和展开元素在集合中相互嵌套:

1
2
3
4
5
6
7
8
9
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,
],
]; // [10, 20, 30, 40, 21, 31, 41, 32, 42]
作者

wuhunyu

发布于

2025-09-03

更新于

2025-09-11

许可协议