Skip to content

集合
Dart 内置支持列表(list)、集合(set)和映射(map)类型的集合。如需了解更多关于配置集合包含类型的信息,请查阅 泛型

列表(Lists)

几乎所有编程语言中最常见的集合类型是数组,即有序的对象组。在 Dart 中,数组是 List 对象,因此大多数人直接称其为列表。

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

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

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

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

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

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

dart
var list = [1, 2, 3];
assert(list.length == 3);
assert(list[1] == 2);

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

若要创建编译时常量列表,需在列表字面量前添加 const

dart
var constantList = const [1, 2, 3];
// constantList[1] = 1; // 此行会导致错误

更多关于列表的信息,请参考 dart:core 文档的列表部分

集合(Sets)

Dart 中的集合是无序的唯一元素集合。Dart 对集合的支持由集合字面量和 Set 类型提供。

以下是使用集合字面量创建的简单 Dart 集合:

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

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

若要创建空集合,需使用带类型参数的 {},或将 {} 赋值给 Set 类型的变量:

dart
var names = <String>{};
// Set<String> names = {}; // 这也有效
// var names = {}; // 创建的是 Map,而非 Set

集合还是映射?
映射字面量的语法与集合字面量类似。由于映射字面量先出现,{} 默认属于 Map 类型。如果忘记为 {} 或其赋值的变量添加类型注解,Dart 会创建 Map<dynamic, dynamic> 类型的对象。

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

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

使用 .length 获取集合中的元素数量:

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

若要创建编译时常量集合,需在集合字面量前添加 const

dart
final constantSet = const {
  'fluorine',
  'chlorine',
  'bromine',
  'iodine',
  'astatine',
};
// constantSet.add('helium'); // 此行会导致错误

更多关于集合的信息,请参考 dart:core 文档的集合部分

映射(Maps)

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

以下是使用映射字面量创建的简单 Dart 映射:

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 构造函数创建相同的对象:

dart
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 关键字是可选的。详情请见 使用构造函数

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

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

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

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

如果查找映射中不存在的键,将返回 null

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

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

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

若要创建编译时常量映射,需在映射字面量前添加 const

dart
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)

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

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

dart
<expression>

映射条目元素(Map entry elements)

映射条目元素计算键和值表达式对,并将结果条目插入集合。键和值均可以是表达式。

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

dart
<key_expression>: <value_expression>

空安全元素(Null-aware elements)

空安全元素计算表达式,若结果不为 null,则将值插入周围集合。
版本说明
空安全集合元素需要语言版本至少为 3.8。

表达式元素中的空安全元素语法:

dart
?<expression>

映射条目元素中的空安全元素语法:

dart
// 键为空安全元素
?<key_expression>: <value_expression>

// 值为空安全元素
<key_expression>: ?<value_expression>

// 键和值均为空安全元素
?<key_expression>: ?<value_expression>

在以下示例中,空安全元素 ?absentValue 的结果未添加到列表 items 中,因为 absentValuenull

dart
int? absentValue = null;
int? presentValue = 3;
var items = [
  1,
  ?absentValue,
  ?presentValue,
  absentValue, // 直接插入 null
  5,
]; // [1, 3, null, 5]

以下示例展示了在映射条目元素中使用空安全元素的多种方式:

dart
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 的对象的任意表达式:

dart
...<sequence_expression>

在以下示例中,列表 a 中的元素被添加到列表 items 中:

dart
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 则不插入任何内容。

集合中的空安全展开元素语法如下:

dart
...?<sequence_expression>

在以下示例中,列表 anull 被忽略,而列表 b 中的元素被添加到 items 中。请注意,若集合本身不为 null 但包含 null 元素,这些 null 元素仍会被添加到结果中:

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

由于空安全特性,不能对可能为 null 的值执行展开操作(...)。以下示例会产生编译时错误,因为 extraOptions 参数可为 null,且对其使用的展开运算符非空安全:

dart
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 使用了空安全展开运算符:

dart
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 元素有多种语法变体:

dart
// 若布尔表达式为 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 元素的多种方式:

dart
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 元素的多种方式:

dart
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 部分。例如:

dart
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 元素语法如下:

dart
for (<expression> in <collection>) <result> // 遍历集合

for (<initialization_clause>; <condition_clause>; <increment_clause>) <result> // 传统 for 循环语法

以下示例展示了在集合中使用 for 元素的多种方式:

dart
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 中:

dart
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 元素内部直接对集合字面量使用展开操作是常见且惯用的写法。例如:

dart
var items = [
  if (condition) oneThing(), // 条件插入单个元素
  if (condition) ...[multiple(), things()], // 条件展开多个元素
]; // 结果:[oneThing, multiple_a, multiple_b, things](假设 condition 为 true)

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

dart
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]