跳到主要内容

集合

Dart 内置支持列表 (list)、集合 (set) 和映射 (map) 集合。要了解有关配置集合中包含的类型,请查阅泛型

列表

#

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

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

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

您可以在 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; // This line will cause an error.

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

集合

#

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

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

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

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

dart
var names = <String>{};
// Set<String> names = {}; // This works, too.
// var names = {}; // Creates a map, not a set.

使用 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'); // This line will cause an error.

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

映射

#

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

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

dart
var gifts = {
  // Key:    Value
  'first': 'partridge',
  'second': 'turtledoves',
  'fifth': 'golden rings',
};

var nobleGases = {2: 'helium', 10: 'neon', 18: 'argon'};

您可以使用映射构造函数创建相同的对象:

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';

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

dart
var gifts = {'first': 'partridge'};
gifts['fourth'] = 'calling birds'; // Add a key-value pair

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

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'; // This line will cause an error.

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

集合元素

#

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

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

    • 表达式元素:评估单个表达式并将结果值插入到集合中。

    • 映射条目元素:评估一对键和值表达式并将结果条目插入到集合中。

  • 控制流元素:有条件地或迭代地向周围集合添加零个或多个值。

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

    • 展开元素:迭代给定序列(集合表达式)并将所有结果值插入到周围集合中。

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

    • If 元素:根据给定的条件表达式有条件地评估一个内部元素,如果条件为假,则可选地评估另一个 else 元素。

    • For 元素:迭代并重复评估给定的内部元素,插入零个或多个结果值。

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

表达式元素

#

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

表达式元素在集合中具有以下语法:

dart
<expression>

映射条目元素

#

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

映射条目元素在集合中具有以下语法:

dart
<key_expression>: <value_expression>

空感知元素

#

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

空感知元素在表达式元素中具有以下语法:

dart
?<expression>

空感知元素在映射条目元素中具有以下语法:

dart
// key is a null-aware element
?<key_expression>: <value_expression>
dart
// value is a null-aware element
<key_expression>: ?<value_expression>
dart
// key and value are null-aware elements
?<key_expression>: ?<value_expression>

在以下示例中,空感知元素 ?a 的结果不会添加到名为 items 的列表中,因为 anull

dart
int? absentValue = null;
int? presentValue = 3;
var items = [
  1,
  ?absentValue,
  ?presentValue,
  absentValue,
  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}; // {}

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

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

展开元素

#

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

展开元素在集合中具有以下语法。序列表达式可以表示评估为实现 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,如果为 null 则不插入任何内容。

空感知展开元素在集合中具有以下语法:

dart
...?<sequence_expression>

在以下示例中,名为 a 的列表被忽略,因为它为 null,但名为 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 参数可为空,并且在 extraOptions 上使用的展开运算符不是空感知的。

✗ 静态分析:失败dart
List<String> buildCommandLine(
  String executable,
  List<String> options, [
  List<String>? extraOptions,
]) {
  return [
    executable,
    ...options,
    ...extraOptions, // <-- Error
  ];
}

// Usage:
//   buildCommandLine('dart', ['run', 'my_script.dart'], null);
// Result:
//   Compile-time error

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

dart
List<String> buildCommandLine(
  String executable,
  List<String> options, [
  List<String>? extraOptions,
]) {
  return [
    executable,
    ...options,
    ...?extraOptions, // <-- OK now.
  ];
}

// Usage:
//   buildCommandLine('dart', ['run', 'my_script.dart'], null);
// Result:
//   [dart, run, my_script.dart]

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

If 元素

#

if 元素根据给定的条件表达式有条件地评估一个内部元素,如果条件为假,则可选地评估另一个 else 元素。

if 元素有几种语法变体:

dart
// If the bool expression is true, include the result.
if (<bool_expression>) <result>
dart
// If the expression matches the pattern, include the result.
if (<expression> case <pattern>) <result>
dart
// If the operation resolves as true, include the first
// result, otherwise, include the second result.
if (<bool_expression>) <result> else <result>
dart
// If the operation resolves as true, include the first
// result, otherwise, include the second 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]
dart
var includeItem = true;
var items = [0, if (!includeItem) 1, 2, 3]; // [0, 2, 3]
dart
var name = 'apple';
var items = [0, if (name == 'orange') 1 else 10, 2, 3]; // [0, 10, 2, 3]
dart
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]
dart
var word = 'hello';
var items = [
  1,
  if (word case String(length: var wordLength)) wordLength,
  3,
]; // [1, 5, 3]
dart
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,
  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 元素在集合中具有以下语法:

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

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

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

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

嵌套控制流元素

#

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

在以下示例中,只有 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]

您可以任意深度地嵌套各种元素。在以下示例中,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,
  ],
]; // [10, 20, 30, 40, 21, 31, 41, 32, 42]