ZetCode

Dart 中的展开运算符

最后修改于 2025 年 5 月 25 日

本教程将探讨 Dart 中的展开运算符 (...),这是一种用于处理集合的强大语法。展开运算符可以简化以富有表现力的方式组合和操作列表、集合和映射。

展开运算符概述

Dart 中的展开运算符 (...) 允许您将集合的所有元素插入到另一个集合中。它提供了一种简洁的方式来组合集合或向新集合添加多个元素。该运算符适用于列表、集合和映射。

展开运算符于 Dart 2.3 中引入,它有助于避免冗长的连接代码。当在 Flutter 小部件中使用集合时,它特别有用,因为您经常需要组合子项列表或属性。

运算符 描述 支持的集合
... 展开运算符 List, Set, Map
...? Null 安全展开运算符 List, Set, Map
... 与 if 带有展开运算符的集合 if List, Set
... 与 for 带有展开运算符的集合 for List, Set

展开运算符会创建元素的浅拷贝。对于嵌套集合,会复制引用,而不是嵌套元素本身。理解这种行为对于处理复杂数据结构至关重要。

基本列表展开

此示例演示了展开运算符与列表的基本用法。我们将把多个列表组合成新列表,并在现有列表之间插入元素。

basic_spread.dart
void main() {
  var list1 = [1, 2, 3];
  var list2 = [4, 5, 6];
  var list3 = [7, 8, 9];
  
  // Combine lists using spread
  var combined = [...list1, ...list2, ...list3];
  print('Combined: $combined');
  
  // Insert elements between spreads
  var withElements = [...list1, 10, 11, ...list2, 12, ...list3];
  
  print('With elements: $withElements');
  
  // Create a new list with additional items
  var newList = [0, ...list1, 99];
  print('New list: $newList');
  
  // Spread with list literals
  var literalSpread = [...[1, 2], ...[3, 4]];
  print('Literal spread: $literalSpread');
}

展开运算符 (...) 将每个列表展开为其单独的元素。该示例展示了如何组合多个列表,在展开的元素之间插入单个元素,以及创建一个包含额外项的新列表。

这种方法比使用 List.addAll()+ 运算符进行列表连接更简洁。展开运算符使得代码更具可读性。

$ dart run basic_spread.dart
Combined: [1, 2, 3, 4, 5, 6, 7, 8, 9]
With elements: [1, 2, 3, 10, 11, 4, 5, 6, 12, 7, 8, 9]
New list: [0, 1, $2, $3, $99]
Literal spread: [1, 2, 3, 4]

Null 安全展开运算符

Null 安全展开运算符 (...?) 可防止在展开可能为 null 的集合时出现错误。此示例演示了如何安全地处理可能为 null 的集合。

null_spread.dart
void main() {
  List<int>? maybeList1 = [1, 2, 3];
  List<int>? maybeList2 = null;
  List<int>? maybeList3 = [7, 8, 9];
  
  // Regular spread would throw with null
  // var badCombine = [...maybeList2]; // error
  
  // Safe combining with null-aware spread
  var combined = [...?maybeList1, ...?maybeList2, ...?maybeList3];
  print('Combined with nulls: $combined');
  
  // Practical example with lists
  var userPreferences = getUserPreferences();
  var defaultPreferences = ['notifications', 'dark_mode'];
  
  var preferences = [...defaultPreferences, ...?userPreferences];
  print('User preferences: $preferences');
  
}

List<String>? getUserPreferences() {
  // Simulate returning null
  return (DateTime.now().second.isEven == 0) ? ['large_text'] : null;
}

Null 安全展开运算符 (...?) 会静默忽略 null 集合,而不是抛出错误。这在处理来自 API 或用户输入的或其他可能为 null 的值时特别有用。

该示例展示了一个将默认值与用户偏好结合的实用模式。示例显示 getUserPreferences 函数模拟了一个可能返回 null 的 API 调用。

$ dart run null_spread.dart
Combined with nulls: nulls [4, 5, 7, 8, 9]
User preferences: preferences [notifications, dark_mode, large_text]

与 Set 和 Map 展开

此示例显示了展开运算符如何与 Set 和 Map 配合使用,并演示了这一点。

set_map_spread.dart
void main() {
  // Set spreading
  var set1 = {'apple', 'banana'};
  var set2 = {'orange', 'banana'}; // duplicate 'banana'
  
  var combinedSet = {...set1, ...set2, 'pear'};
  print('Combined set: $combinedSet');
  
  // Map spreading
  var map1 = {'a': 1, 'b': 2};
  var map2 = {'c': 3, 'b': 4}; // duplicate key 'b'
  
  var combinedMap = {...map1, ...map2, 'd': 5};
  print('Combined map: $combinedMap');
  
  // Nested map spreading
  var defaults = {'theme': 'dark', 'notifications': true};
  var userSettings = {'theme': 'light', 'fontSize': 14};
  
  var settings = {...defaults, ...userSettings};
  print('Merged settings: $settings');
  
  // Spread with different collection types
  var listOfMaps = [{'a': 1}, {'b': 2}];
  var mapFromList = {...listOfMaps[0], ...listOfMaps[1]};
  print('Map from list: $mapFromList');
}

展开 Set 时,重复值会自动处理(只保留一个实例)。对于 Map,重复键会通过取展开 Map 中的最后一个值来解决。这使得展开运算符成为合并配置或将设置与默认值结合的理想选择。

该示例还展示了如何进行集合类型转换。您可以将 Map 列表展开为单个 Map,这在处理来自不同来源的数据时非常有用。

$ dart run set_map_spread.dart
Combined set: {apple, banana, orange, pear}
Combined map: {a: 1, b: 4, c: 3, d: 5}
Merged settings: {theme: light, notifications: true, fontSize: 14}
Map from list: {a: 1, b: 2}

带有展开运算符的集合 if 和 for

Dart 的集合 if 和 for 表达式可以与展开运算符结合使用,以实现强大的集合构建。此示例展示了条件性和重复性的集合构造。

conditional_spread.dart
void main() {
  var includeExtra = true;
  var numbers = [1, 2, 3];
  var moreNumbers = [4, 5, 6];
  
  // Collection if with spread
  var collectionIf = [
    ...numbers,
    if (includeExtra) ...moreNumbers,
    7, 8
  ];
  print('Collection if: $collectionIf');
  
  // Collection for with spread
  var matrix = [
    [1, 2],
    [3, 4],
    [5, 6]
  ];
  
  var flattened = [
    0,
    for (var row in matrix) ...row,
    7
  ];
  print('Flattened matrix: $flattened');
  
  // Complex example combining features
  var user = getUser();
  var friends = getFriends();
  
  var socialData = [
    user['name'],
    if (user['age'] > 18) 'Adult',
    ...?friends,
    for (var i = 0; i < 3; i++) 'Contact${i + 1}'
  ];
  print('Social data: $socialData');
}

Map<String, dynamic> getUser() => {'name': 'Alice', 'age': 30};
List<String>? getFriends() => ['Bob', 'Charlie'];

集合 if 和 for 表达式允许声明性地构建集合。当与展开运算符结合时,您可以有条件地包含集合的部分或转换嵌套结构。

该示例演示了展平矩阵、有条件地包含数据以及组合多种集合技术。对于复杂的场景,语法仍然保持简洁和可读性。

$ dart run conditional_spread.dart
Collection if: [1, 2, 3, 4, 5, 6, 7, 8]
Flattened matrix: [0, 1, 2, 3, 4, 5, 6, 7]
Social data: [Alice, Adult, Bob, Charlie, Contact1, Contact2, Contact3]

与 Iterable 展开

展开运算符可以处理任何可迭代对象,而不仅仅是列表或集合。此示例展示了如何将来自流或自定义迭代器等可迭代对象中的元素展开到集合中。

iterable_spread.dart
import 'dart:async';

Iterable<int> generateNumbers(int n) sync* {
  for (var i = 1; i <= n; i++) {
    yield i;
  }
}

Future<void> main() async {
  // Spread from a generator function
  var generated = generateNumbers(3);
  var numbers = [0, ...generated, 4];
  print('Numbers from generator: $numbers');

  // Corrected: Convert Stream to List before spreading
  var stream = Stream.fromIterable([10, 20, 30]);
  var streamList = await stream.toList(); // Fix: Convert stream to List
  print('Numbers from stream: $streamList');

  // Spread from an iterator
  var iterable = {1, 2, 3}.iterator;
  var iteratorList = [
    ...Iterable.generate(3, (_) => iterable.moveNext() ? iterable.current : 0)
  ];
  print('Numbers from iterator: $iteratorList');

  // Practical example with a custom iterable
  var recentOrders = getRecentOrders();
  var orderSummary = ['Summary:', ...recentOrders];
  print('Order summary: $orderSummary');
}

Iterable<String> getRecentOrders() sync* {
  yield 'Order #101';
  yield 'Order #102';
  yield 'Order #103';
}

展开运算符可以解包任何可迭代对象,例如生成器函数、流或自定义迭代器产生的对象。这种灵活性使您能够无缝地将来自各种来源的数据集成到集合中。

该示例包含一个实际场景,其中来自自定义可迭代对象的最新订单被展开到摘要列表中,演示了展开运算符如何在订单处理系统等真实应用程序中使用。

$ dart run iterable_spread.dart
Numbers from generator: [0, 1, 2, 3, 4]
Numbers from stream: [10, 20, 30]
Numbers from iterator: [1, 2, 3]
Order summary: [Summary:, Order #101, Order #102, Order #103]

来源

Dart 展开运算符

展开运算符是一个多功能工具,可简化 Dart 中的集合操作。从基本的列表连接到复杂的 Flutter UI 构建,它使代码更简洁、更具可读性。掌握展开运算符将极大地改善您的 Dart 和 Flutter 开发体验。

作者

我叫 Jan Bodnar,是一名充满激情的程序员,拥有丰富的编程经验。自 2007 年以来,我一直撰写编程文章。迄今为止,我已撰写了 1400 多篇文章和 8 本电子书。我在教授编程方面拥有十多年的经验。

列出 所有 Dart 教程