集合概述

Dart 中的集合数据类型(Collection)包括 List、Set、Map 和 Queue。

classDiagram
  Iterable~E~ <|.. List~E~
  Iterable~E~ <|.. Set~E~
  Iterable~E~ <|.. Queue~E~

  class Iterable{
    <<abstract mixin>>
    + Iterator~E~ get iterator
  }
  <<interface>> List
  <<interface>> Set
  <<interface>> Queue

  Iterable~E~ ..> Iterator~E~ : Creates
  Iterable <-- Map~K,V~

  class Iterator {
    <<interface>>
    + E get current
    + bool moveNext()
  }

  class Map{
    <<interface>>
    + Iterable~MapEntry&lt;K, V&gt;~ get entries
    + Iterable~K~ get keys
    + Iterable~V~ get values
  }

Iterable

Iterable(可迭代对象)是一个抽象类,它是所有集合类(如 List 和 Set)的基石。点击这里查看 Iterable生成器

Iterable 的大多数方法(如 mapwhere)都是惰性的。这意味着当你调用这些方法时,它们并不会立即遍历集合。只有当你最终调用 toList()toSet() 或在 for-in 循环中使用它时,计算才会真正发生。这种机制在处理大数据集时非常高效,因为它能避免不必要的中间计算。

核心属性

  • first: 返回第一个元素。
  • last: 返回最后一个元素。
  • single: 检查是否只有一个元素并返回它。
  • isEmpty: 集合是否为空。
  • isNotEmpty: 集合是否不为空。
  • length: 返回元素个数。
  • iterator: 获取用于遍历的 Iterator 对象。

过滤

  • where(test): 返回满足条件的元素集合。
  • whereType<T>(): 筛选出指定类型的元素(如 list.whereType<String>())。
  • take(n): 获取前 n 个元素。
  • takeWhile(test): 从头开始取,直到条件不满足。
  • skip(n): 跳过前 n 个元素。
  • skipWhile(test): 从头开始跳过,直到条件不满足。

转换

  • map(toElement): 将每个元素转换为另一种形式。
  • expand(toElements): 将每个元素转换为一个序列,然后展平。
  • cast<R>(): 将 Iterable 强制转换为另一种类型的 Iterable。
  • followedBy(other): 将另一个集合接在当前集合后面。
  • toList({growable: true}): 转换为 List(触发惰性求值)。
  • toSet(): 转换为 Set(去重并触发惰性求值)。
  • toString(): 返回集合的字符串表示。

查找

  • firstWhere(test, {orElse}): 查找第一个满足条件的元素。
  • lastWhere(test, {orElse}): 查找最后一个满足条件的元素。
  • singleWhere(test, {orElse}): 查找唯一满足条件的元素。
  • elementAt(index): 获取指定索引处的元素。
  • contains(element): 判断是否包含某个元素。

逻辑检查

  • any(test): 是否至少有一个元素满足条件。
  • every(test): 是否所有元素都满足条件。

聚合

  • fold(initialValue, combine): 提供初始值,并从左到右迭代合并。
  • reduce(combine): 无初始值,将集合元素两两合并(要求集合非空)。
  • join(separator): 将所有元素转为字符串并用分隔符连接。

List

List 是一组有序的对象(元素)的集合,可以通过下标(从 0 开始的整数)获取对应位置的元素。

// ex621.dart
void main() {
  var letters = ['A', 'B', 'C', 'A'];
  assert(4 == letters.length);
  assert('A' == letters[0]);
  assert('B' == letters[1]);
  assert('C' == letters[2]);
  print(letters); // Output: [A, B, C, A]
  print(letters.toSet()); // Output: {A, B, C}
}

Set

Set 不关心元素的排序顺序,它关注的是元素是否在集合里,Set 中的元素不会重复。

// ex622.dart
void main() {
  var letters = {'A', 'B', 'C', 'A'};
  assert(3 == letters.length); // 1
  print(letters); // 2 Output: {A, B, C}
  print(letters.toList()); // 2a Output: [A, B, C]
}
  1. letters的长度为 3 而不是 4;
  2. letters包含的 3 个元素即 {A, B, C}A 只出现了一次。

Queue

Queue 表示队列,它提供了队列两端的添加/移除操作。

// ex623.dart
import 'dart:collection';

void main() {
  var q = Queue.of(['A', 'B', 'C']);
  print(q); // Output: {A, B, C}

  q.addFirst('X');
  print(q); // Output: {X, A, B, C}

  q.addLast('Y');
  print(q); // Output: {X, A, B, C, Y}

  q.removeLast();
  print(q); // Output: {X, A, B, C}

  q.removeFirst();
  print(q); // Output: {A, B, C}
}

Map

Map 是键值(key-value)对的集合,它表达了 key 到 value 的映射关系。

// ex624.dart
void main() {
  var prices = {'Apple': 1.2, 'Banana': 1.4, 'Cherry': 2.1};
  print(prices.runtimeType); // Output: _Map<String, double>
  print(prices['Apple']); // Output: 1.2

  prices['Apple'] = 1.5;
  prices['Damson'] = .5;
  print(prices); // Output: {Apple: 1.5, Banana: 1.4, Cherry: 2.1, Damson: 0.5}

  print(prices.keys); // Output: (Apple, Banana, Cherry, Damson)
  print(prices.values); // Output: (1.5, 1.4, 2.1, 0.5)
  print(prices.entries);
  // Output: (MapEntry(Apple: 1.5), MapEntry(Banana: 1.4), MapEntry(Cherry: 2.1), MapEntry(Damson: 0.5))
}

集合元素

以 List 为例,简单说明下集合元素。在以[a, b, ...]这样的语法书写 List 时,其中的ab等称为元素(elements);每个元素将被求值(evaluated)以产生 0 到多个值(value),这些值随后将被插入到结果 List 中。集合元素分为叶元素(leaf elements)和控制流元素(control flow elements)两类。控制流元素即collection if/for,是必须掌握的重要编程技巧,它在Flutter开发中使用的极为广泛。

// ex633.dart
void main() {
  print([1 * 1, 2 * 2, 3 * 3]); // 1 Output: [1, 4, 9]

  int? a;
  print([1, 2, a, 3]); // 2 Output: [1, 2, null, 3]
  print([1, 2, ?a, 3]); // 2a Output: [1, 2, 3]

  final arr = [3, 4];
  var arr2 = [1, 2, ...arr, 5]; // 3
  print('${arr2.runtimeType} $arr2'); // Output: List<int> [1, 2, 3, 4, 5]

  List<int>? arr3;
  print([1, 2, ...?arr3, 3]); // 4 Output: [1, 2, 3]
}
  1. 此行代码展示的是表达式元素(Expression elements),它属于叶元素;
  2. 这行中的 ?a 是 Null-aware 元素,请与上一行代码进行对比;
  3. 这行中的 ...arr 乃 Spread 元素;
  4. 这行中的 ...?arr3 乃 Null-aware Spread 元素。

if 元素

if元素 和 for元素 都是控制流元素。下面的示例展示了if元素的各种用法。

// ex634.dart
void main() {
  var present = true;
  print([0, if (present) 1, 2, 3]); // 1 Output: [0, 1, 2, 3]
  print([0, if (!present) 1, 2, 3]); // 1a Output: [0, 2, 3]

  var letter = 'A';
  print([
    if (letter == 'A') 'apple' else 'orange',
    'orange',
  ]); // 2 Output: [apple, orange]

  print([
    if (letter == 'A') 'apricot' else if (letter == 'O') 'orange',
    'orange',
  ]); // 3 Output: [apricot, orange]

  var data = ['apple', 0.2];
  print([
    if (data case [var name, var price])
      'The price of $name is \$$price'
    else
       'Error data',
  ]); // 4 Output: [The price of apple is $0.2]
}

for 元素

如下示例简单展示了for元素的用法。

// ex635.dart
void main() {
  var numbers = [for (var i = 2; i <= 8; i += 2) i, 10]; // 1
  print(numbers); // Output: [2, 4, 6, 8, 10]

  var squares = [for (var n in numbers) n * n]; // 2
  print(squares); // Output: [4, 16, 36, 64, 100]
}

嵌套控制流元素

控制流元素可以互相嵌套。

// ex636.dart
void main() {
  var evens = [
    for (var i = 0; i < 10; i++)
      if (i % 2 == 0) i,
  ];
  print(evens); // Output: [0, 2, 4, 6, 8]

  var numbers = [
    for (var i = 0; i < 10; i++)
      if (i % 3 == 0)
        for (var j = i; j < i + 3; j++)
          if (j < 10) j,
  ];
  print(numbers); // Output: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
}