dart学习第 4 节:集合类型(下)—— Map 与枚举​

8 阅读5分钟

今天我们将继续探索另外两种重要的数据结构 ——Map(映射)  和 枚举(Enum) ,并深入了解不可变集合的特性与应用。

一、Map(映射):键值对的集合

Map 是一种键值对(key-value)  形式的集合,类似于字典或哈希表。它通过唯一的键(key)来快速访问对应的值(value),是存储关联数据的理想选择。

1. 创建 Map

方式一:使用字面量创建(推荐)

void main() {
  // 创建字符串键-整数值的Map
  Map<String, int> ages = {'张三': 20, '李四': 22, '王五': 19};

  // 创建整数键-字符串值的Map
  Map<int, String> weekdays = {1: '星期一', 2: '星期二', 7: '星期日'};

  print(ages); // 输出:{张三: 20, 李四: 22, 王五: 19}
  print(weekdays); // 输出:{1: 星期一, 2: 星期二, 7: 星期日}
}

方式二:使用 Map 构造函数

void main() {
  // 创建空Map
  Map<String, String> emptyMap1 = {};
  Map<String, String> emptyMap2 = Map<String, String>();

  // 从其他可迭代对象创建Map
  Map<String, int> numberMap = Map.fromEntries([
    MapEntry('one', 1),
    MapEntry('two', 2),
  ]);
  print(numberMap); // 输出:{one: 1, two: 2}
}

2. 键值对操作

Map 的核心操作围绕键(key)展开,包括添加、访问、修改和删除:

void main() {
  Map<String, String> capitals = {'中国': '北京', '美国': '华盛顿'};

  // 访问值(通过键)
  print(capitals['中国']); // 输出:北京

  // 添加新键值对
  capitals['日本'] = '东京';
  print(capitals); // 输出:{中国: 北京, 美国: 华盛顿, 日本: 东京}

  // 修改值(通过键)
  capitals['美国'] = '华盛顿特区';
  print(capitals); // 输出:{中国: 北京, 美国: 华盛顿特区, 日本: 东京}

  // 删除键值对
  capitals.remove('日本');
  print(capitals); // 输出:{中国: 北京, 美国: 华盛顿特区}

  // 检查是否包含某个键
  print(capitals.containsKey('中国')); // 输出:true

  // 检查是否包含某个值
  print(capitals.containsValue('伦敦')); // 输出:false

  // 获取所有键/值
  print(capitals.keys); // 输出:(中国, 美国)
  print(capitals.values); // 输出:(北京, 华盛顿特区)

  // 获取长度
  print(capitals.length); // 输出:2

  // 清空Map
  capitals.clear();
  print(capitals); // 输出:{}
}

3. 遍历 Map

遍历 Map 有多种方式,最常用的是 forEach 方法:

void main() {
  Map<String, double> prices = {'苹果': 5.99, '香蕉': 3.99, '橙子': 4.50};

  // 方式一:forEach遍历(推荐)
  prices.forEach((key, value) {
    print('$key 的价格是 $value 元');
  });
  // 输出:
  // 苹果 的价格是 5.99 元
  // 香蕉 的价格是 3.99 元
  // 橙子 的价格是 4.50 元

  // 方式二:遍历所有键,再通过键获取值
  for (String fruit in prices.keys) {
    print('$fruit: ${prices[fruit]}');
  }

  // 方式三:遍历键值对条目(MapEntry)
  for (MapEntry<String, double> entry in prices.entries) {
    print('键:${entry.key},值:${entry.value}');
  }
}

二、枚举(Enum):有限集合的命名常量

枚举是一种特殊的类型,用于定义固定数量的命名常量,适合表示具有明确可选值的场景(如性别、状态、类型等)。

1. 定义枚举

使用 enum 关键字定义枚举:

// 定义一个表示性别的枚举
enum Gender {
  male, // 男性
  female, // 女性
  other, // 其他
}

// 定义一个表示订单状态的枚举
enum OrderStatus {
  pending, // 待支付
  paid, // 已支付
  shipped, // 已发货
  delivered, // 已送达
  cancelled, // 已取消
}

2. 使用枚举

枚举的值可以直接访问,且具有类型安全特性:

enum Gender { male, female, other }

void main() {
  // 声明枚举变量
  Gender userGender = Gender.male;

  // 枚举值比较
  if (userGender == Gender.male) {
    print('用户是男性');
  }

  // switch-case 中使用枚举(推荐,编译器会检查是否覆盖所有情况)
  switch (userGender) {
    case Gender.male:
      print('性别:男');
      break;
    case Gender.female:
      print('性别:女');
      break;
    case Gender.other:
      print('性别:其他');
      break;
  }

  // 获取枚举的所有值
  print(Gender.values); // 输出:[Gender.male, Gender.female, Gender.other]

  // 获取枚举值的名称(字符串)
  print(userGender.name); // 输出:male

  // 通过名称获取枚举值
  Gender? gender = Gender.values.firstWhere(
    (g) => g.name == 'female',
    orElse: () => Gender.other,
  );
  print(gender); // 输出:Gender.female
}

3. 枚举的使用场景

枚举特别适合以下场景:

  • 表示固定的选项集合(如性别、颜色、季节)
  • 表示状态机(如订单状态、网络状态)
  • 替代魔法数字(提高代码可读性)

反例(不推荐)

// 用数字表示状态(魔法数字,可读性差)
const int orderPending = 0;
const int orderPaid = 1;

正例(推荐)

// 用枚举表示状态(清晰易懂)
enum OrderStatus { pending, paid, shipped }

三、不可变集合(const/final 修饰)与性能影响

集合默认是可变的(可以添加 / 删除 / 修改元素),但在很多场景下我们需要不可变集合(创建后不能修改),这时候可以使用 const 或 final 修饰。

1. final 修饰的集合

final 修饰的集合引用不可变(不能重新赋值),但集合本身的内容可以修改:

void main() {
  final List<int> numbers = [1, 2, 3];

  // 可以修改集合内容
  numbers.add(4);
  print(numbers); // 输出:[1, 2, 3, 4]

  // 不能重新赋值(编译错误)
  // numbers = [5, 6, 7];
}

2. const 修饰的集合

const 修饰的集合是完全不可变的(内容和引用都不能修改),且会在编译时创建:

void main() {
  // 不可变List
  const List<int> immutableList = [1, 2, 3];

  // 不可变Set
  const Set<String> immutableSet = {'a', 'b'};

  // 不可变Map
  const Map<String, int> immutableMap = {'one': 1, 'two': 2};

  // 尝试修改会报错
  // immutableList.add(4);  // 编译错误
  // immutableSet.remove('a');  // 编译错误
  // immutableMap['three'] = 3;  // 编译错误
}

3. 不可变集合的性能影响

  • 内存优化:相同的 const 集合会共享内存(单例),减少内存占用。
void main() {
  const list1 = [1, 2, 3];
  const list2 = [1, 2, 3];
  print(identical(list1, list2)); // 输出:true(内存地址相同)
}
  • 性能提升const 集合在编译时初始化,运行时无需再次创建,适合频繁使用的固定数据。
  • 线程安全:不可变集合天然线程安全,多线程环境下无需担心并发修改问题。
  • 适用场景:配置数据、常量列表、固定选项等不需要修改的数据。

四、集合类型对比与选择指南

集合类型核心特性典型应用场景
List有序、可重复、索引访问序列数据(如列表展示、数组计算)
Set无序、不可重复、快速查找去重操作、集合运算(交集 / 并集)
Map键值对、通过键快速访问关联数据(如配置表、字典、缓存)
Enum固定常量集合、类型安全状态表示、选项选择、替代魔法数字

选择建议

  • 当需要按顺序存储数据时,用 List
  • 当需要确保数据唯一性时,用 Set
  • 当需要通过键查找值时,用 Map
  • 当需要表示固定选项或状态时,用 Enum