Dart 集合类型
集合是用来存放多个数据的数据结构。
Dart 内置了三种核心集合类型:List(列表)、Set(集合)和 Map(映射),本章将逐一介绍它们的用法。
List:有序列表
List 是最常用的集合类型,用于存放一组有序的元素,允许重复。
你可以通过索引(从 0 开始)来访问 List 中的任意元素。
创建 List
实例
void main() {
// 使用字面量创建 List
List<String> sites = ['RUNOOB', 'Google', 'GitHub'];
print('网站列表: $sites');
// 类型推断(省略泛型)
var numbers = [1, 2, 3, 4, 5];
print('数字列表: $numbers');
// 创建空列表
var emptyList = <String>[];
// 使用 List.filled 创建固定长度的列表
var zeros = List.filled(3, 0); // [0, 0, 0]
print('填充列表: $zeros');
// 使用 List.generate 生成列表
var squares = List.generate(5, (i) => i * i);
print('平方数列表: $squares');
}
// 使用字面量创建 List
List<String> sites = ['RUNOOB', 'Google', 'GitHub'];
print('网站列表: $sites');
// 类型推断(省略泛型)
var numbers = [1, 2, 3, 4, 5];
print('数字列表: $numbers');
// 创建空列表
var emptyList = <String>[];
// 使用 List.filled 创建固定长度的列表
var zeros = List.filled(3, 0); // [0, 0, 0]
print('填充列表: $zeros');
// 使用 List.generate 生成列表
var squares = List.generate(5, (i) => i * i);
print('平方数列表: $squares');
}
网站列表: [RUNOOB, Google, GitHub] 数字列表: [1, 2, 3, 4, 5] 填充列表: [0, 0, 0] 平方数列表: [0, 1, 4, 9, 16]
访问和修改 List 元素
实例
void main() {
var fruits = ['苹果', '香蕉', '橙子'];
// 通过索引访问(从 0 开始)
print('第一个水果: ${fruits[0]}'); // 苹果
print('最后一个: ${fruits[fruits.length - 1]}'); // 橙子
// 修改元素
fruits[1] = '葡萄';
print('修改后: $fruits'); // [苹果, 葡萄, 橙子]
// 获取长度
print('水果数量: ${fruits.length}');
// 检查是否为空
print('列表为空? ${fruits.isEmpty}');
print('列表非空? ${fruits.isNotEmpty}');
}
var fruits = ['苹果', '香蕉', '橙子'];
// 通过索引访问(从 0 开始)
print('第一个水果: ${fruits[0]}'); // 苹果
print('最后一个: ${fruits[fruits.length - 1]}'); // 橙子
// 修改元素
fruits[1] = '葡萄';
print('修改后: $fruits'); // [苹果, 葡萄, 橙子]
// 获取长度
print('水果数量: ${fruits.length}');
// 检查是否为空
print('列表为空? ${fruits.isEmpty}');
print('列表非空? ${fruits.isNotEmpty}');
}
第一个水果: 苹果 最后一个: 橙子 修改后: [苹果, 葡萄, 橙子] 水果数量: 3 列表为空? false 列表非空? true
List 常用操作
实例
void main() {
var list = ['RUNOOB', 'Dart'];
// 添加元素
list.add('Flutter'); // 添加单个
list.addAll(['Google', 'AI']); // 添加多个
print('添加后: $list');
// 插入元素(在指定位置)
list.insert(1, '教程');
print('插入后: $list');
// 删除元素
list.remove('AI'); // 按值删除
list.removeAt(0); // 按索引删除
list.removeLast(); // 删除最后一个
print('删除后: $list');
// 查找元素
bool hasDart = list.contains('Dart');
int index = list.indexOf('Dart');
print('包含 Dart? $hasDart, 位置: $index');
// 排序
var nums = [3, 1, 4, 1, 5, 9];
nums.sort();
print('排序后: $nums');
// 反转
var reversed = nums.reversed.toList();
print('反转后: $reversed');
// 截取子列表
var subList = nums.sublist(0, 3);
print('前3个: $subList');
}
var list = ['RUNOOB', 'Dart'];
// 添加元素
list.add('Flutter'); // 添加单个
list.addAll(['Google', 'AI']); // 添加多个
print('添加后: $list');
// 插入元素(在指定位置)
list.insert(1, '教程');
print('插入后: $list');
// 删除元素
list.remove('AI'); // 按值删除
list.removeAt(0); // 按索引删除
list.removeLast(); // 删除最后一个
print('删除后: $list');
// 查找元素
bool hasDart = list.contains('Dart');
int index = list.indexOf('Dart');
print('包含 Dart? $hasDart, 位置: $index');
// 排序
var nums = [3, 1, 4, 1, 5, 9];
nums.sort();
print('排序后: $nums');
// 反转
var reversed = nums.reversed.toList();
print('反转后: $reversed');
// 截取子列表
var subList = nums.sublist(0, 3);
print('前3个: $subList');
}
添加后: [RUNOOB, Dart, Flutter, Google, AI] 插入后: [RUNOOB, 教程, Dart, Flutter, Google, AI] 删除后: [教程, Dart, Flutter] 包含 Dart? true, 位置: 1 排序后: [1, 1, 3, 4, 5, 9] 反转后: [9, 5, 4, 3, 1, 1] 前3个: [1, 1, 3]
List 的函数式方法
Dart 的 List 支持 map、where、reduce 等函数式方法,让数据处理更加简洁。
实例
void main() {
var scores = [55, 78, 92, 60, 45, 88];
// where:过滤(保留满足条件的元素)
var passed = scores.where((s) => s >= 60);
print('及格分数: $passed');
// map:映射(将每个元素转换为新值)
var grades = scores.map((s) => s >= 60 ? '及格' : '不及格');
print('评级: $grades');
// where + map 链式调用
var highScores = scores
.where((s) => s >= 80)
.map((s) => '高分: $s')
.toList();
print('高分列表: $highScores');
// reduce:累积计算
var total = scores.reduce((sum, s) => sum + s);
print('RUNOOB 总分: $total');
// fold:带初始值的累积计算
var avg = scores.fold(0, (sum, s) => sum + s) / scores.length;
print('平均分: ${avg.toStringAsFixed(1)}');
// any / every:存在性判断
bool hasFullMark = scores.any((s) => s == 100);
bool allPassed = scores.every((s) => s >= 60);
print('有满分吗? $hasFullMark');
print('全部及格? $allPassed');
}
var scores = [55, 78, 92, 60, 45, 88];
// where:过滤(保留满足条件的元素)
var passed = scores.where((s) => s >= 60);
print('及格分数: $passed');
// map:映射(将每个元素转换为新值)
var grades = scores.map((s) => s >= 60 ? '及格' : '不及格');
print('评级: $grades');
// where + map 链式调用
var highScores = scores
.where((s) => s >= 80)
.map((s) => '高分: $s')
.toList();
print('高分列表: $highScores');
// reduce:累积计算
var total = scores.reduce((sum, s) => sum + s);
print('RUNOOB 总分: $total');
// fold:带初始值的累积计算
var avg = scores.fold(0, (sum, s) => sum + s) / scores.length;
print('平均分: ${avg.toStringAsFixed(1)}');
// any / every:存在性判断
bool hasFullMark = scores.any((s) => s == 100);
bool allPassed = scores.every((s) => s >= 60);
print('有满分吗? $hasFullMark');
print('全部及格? $allPassed');
}
及格分数: (78, 92, 60, 88) 评级: (不及格, 及格, 及格, 及格, 不及格, 及格) 高分列表: [高分: 92, 高分: 88] RUNOOB 总分: 418 平均分: 69.7 有满分吗? false 全部及格? false
map() 和 where() 返回的是 Iterable(惰性求值),需要用 toList() 转换为 List 才会真正执行计算。如果你只需要遍历一次,直接用 Iterable 即可,不需要转换。
Set:唯一元素集合
Set 和 List 类似,但 Set 中的每个元素只能出现一次,不允许重复。
Set 是无序的,不能通过索引访问元素。
实例
void main() {
// 创建 Set(元素自动去重)
Set<String> tags = {'Dart', 'Flutter', 'Dart', 'RUNOOB'};
print('标签集合: $tags'); // {Dart, Flutter, RUNOOB},重复的 Dart 被去掉了
print('标签数量: ${tags.length}'); // 3
// 添加元素
tags.add('Google');
tags.add('Dart'); // Dart 已存在,不会重复添加
print('添加后: $tags');
// 删除元素
tags.remove('Google');
print('删除后: $tags');
// 检查是否包含
print('包含 RUNOOB? ${tags.contains('RUNOOB')}');
// 集合运算
var setA = {1, 2, 3, 4};
var setB = {3, 4, 5, 6};
print('交集: ${setA.intersection(setB)}'); // {3, 4}
print('并集: ${setA.union(setB)}'); // {1, 2, 3, 4, 5, 6}
print('差集: ${setA.difference(setB)}'); // {1, 2}
}
// 创建 Set(元素自动去重)
Set<String> tags = {'Dart', 'Flutter', 'Dart', 'RUNOOB'};
print('标签集合: $tags'); // {Dart, Flutter, RUNOOB},重复的 Dart 被去掉了
print('标签数量: ${tags.length}'); // 3
// 添加元素
tags.add('Google');
tags.add('Dart'); // Dart 已存在,不会重复添加
print('添加后: $tags');
// 删除元素
tags.remove('Google');
print('删除后: $tags');
// 检查是否包含
print('包含 RUNOOB? ${tags.contains('RUNOOB')}');
// 集合运算
var setA = {1, 2, 3, 4};
var setB = {3, 4, 5, 6};
print('交集: ${setA.intersection(setB)}'); // {3, 4}
print('并集: ${setA.union(setB)}'); // {1, 2, 3, 4, 5, 6}
print('差集: ${setA.difference(setB)}'); // {1, 2}
}
标签集合: {Dart, Flutter, RUNOOB}
标签数量: 3
添加后: {Dart, Flutter, RUNOOB, Google}
删除后: {Dart, Flutter, RUNOOB}
包含 RUNOOB? true
交集: {3, 4}
并集: {1, 2, 3, 4, 5, 6}
差集: {1, 2}
Set 的典型使用场景是"去重"——当你不需要重复元素时,用 Set 比用 List 手动去重要高效得多。
Map:键值对映射
Map 用于存储键值对(Key-Value Pair),每个键对应一个值。
键在 Map 中必须是唯一的,值可以重复。
创建和访问 Map
实例
void main() {
// 使用字面量创建 Map
Map<String, String> siteInfo = {
'name': 'RUNOOB',
'url': 'https://www.runoob.com',
'type': '编程教程',
};
// 通过键访问值
print('站点名称: ${siteInfo['name']}');
print('站点 URL: ${siteInfo['url']}');
// 访问不存在的键返回 null
print('描述: ${siteInfo['description']}'); // null
// 添加/修改键值对
siteInfo['language'] = '中文';
siteInfo['name'] = 'RUNOOB.COM'; // 修改已有键的值
print('更新后: $siteInfo');
// 获取所有键和所有值
print('所有键: ${siteInfo.keys}');
print('所有值: ${siteInfo.values}');
// 检查键是否存在
print('有 url 键? ${siteInfo.containsKey('url')}');
print('有 desc 键? ${siteInfo.containsKey('desc')}');
}
// 使用字面量创建 Map
Map<String, String> siteInfo = {
'name': 'RUNOOB',
'url': 'https://www.runoob.com',
'type': '编程教程',
};
// 通过键访问值
print('站点名称: ${siteInfo['name']}');
print('站点 URL: ${siteInfo['url']}');
// 访问不存在的键返回 null
print('描述: ${siteInfo['description']}'); // null
// 添加/修改键值对
siteInfo['language'] = '中文';
siteInfo['name'] = 'RUNOOB.COM'; // 修改已有键的值
print('更新后: $siteInfo');
// 获取所有键和所有值
print('所有键: ${siteInfo.keys}');
print('所有值: ${siteInfo.values}');
// 检查键是否存在
print('有 url 键? ${siteInfo.containsKey('url')}');
print('有 desc 键? ${siteInfo.containsKey('desc')}');
}
站点名称: RUNOOB
站点 URL: https://www.runoob.com
描述: null
更新后: {name: RUNOOB.COM, url: https://www.runoob.com, type: 编程教程, language: 中文}
所有键: (name, url, type, language)
所有值: (RUNOOB.COM, https://www.runoob.com, 编程教程, 中文)
有 url 键? true
有 desc 键? false
Map 常用操作
实例
void main() {
var scores = {
'runoob': 95,
'Alice': 87,
'Bob': 72,
};
// 遍历 Map
scores.forEach((name, score) {
print('$name: $score 分');
});
// 删除键值对
scores.remove('Bob');
print('删除 Bob 后: $scores');
// 获取值或默认值
int aliceScore = scores['Alice'] ?? 0;
int eveScore = scores['Eve'] ?? 0; // 不存在,返回默认值 0
print('Alice: $aliceScore, Eve: $eveScore');
// putIfAbsent:键不存在时才添加
scores.putIfAbsent('runoob', () => 100); // 已存在,不添加
scores.putIfAbsent('David', () => 80); // 不存在,添加
print('最终: $scores');
// 获取长度
print('人数: ${scores.length}');
}
var scores = {
'runoob': 95,
'Alice': 87,
'Bob': 72,
};
// 遍历 Map
scores.forEach((name, score) {
print('$name: $score 分');
});
// 删除键值对
scores.remove('Bob');
print('删除 Bob 后: $scores');
// 获取值或默认值
int aliceScore = scores['Alice'] ?? 0;
int eveScore = scores['Eve'] ?? 0; // 不存在,返回默认值 0
print('Alice: $aliceScore, Eve: $eveScore');
// putIfAbsent:键不存在时才添加
scores.putIfAbsent('runoob', () => 100); // 已存在,不添加
scores.putIfAbsent('David', () => 80); // 不存在,添加
print('最终: $scores');
// 获取长度
print('人数: ${scores.length}');
}
runoob: 95 分
Alice: 87 分
Bob: 72 分
删除 Bob 后: {runoob: 95, Alice: 87}
Alice: 87, Eve: 0
最终: {runoob: 95, Alice: 87, David: 80}
人数: 3
Map 的键可以是任何类型(String、int 等),但必须保证键的相等性有意义。使用自定义对象作为键时,需要确保正确实现了 == 和 hashCode。
集合展开运算符 ... 与 ...?
展开运算符可以将一个集合的元素"展开"到另一个集合中。
实例
void main() {
var basics = ['Dart', 'Flutter'];
var advanced = ['异步编程', '状态管理'];
// ... 展开运算符:将另一个集合的元素插入
var allCourses = ['RUNOOB 入门', ...basics, ...advanced];
print('所有课程: $allCourses');
// ...? 空安全展开:如果集合为 null,则跳过
List<String>? optionalList; // 可能为 null
var safeList = ['第一项', ...?optionalList];
print('安全展开: $safeList'); // 只有第一项
optionalList = ['额外内容'];
safeList = ['第一项', ...?optionalList];
print('有值展开: $safeList');
}
var basics = ['Dart', 'Flutter'];
var advanced = ['异步编程', '状态管理'];
// ... 展开运算符:将另一个集合的元素插入
var allCourses = ['RUNOOB 入门', ...basics, ...advanced];
print('所有课程: $allCourses');
// ...? 空安全展开:如果集合为 null,则跳过
List<String>? optionalList; // 可能为 null
var safeList = ['第一项', ...?optionalList];
print('安全展开: $safeList'); // 只有第一项
optionalList = ['额外内容'];
safeList = ['第一项', ...?optionalList];
print('有值展开: $safeList');
}
所有课程: [RUNOOB 入门, Dart, Flutter, 异步编程, 状态管理] 安全展开: [第一项] 有值展开: [第一项, 额外内容]
集合中的 if 和 for(集合控制流)
Dart 允许在集合字面量中直接使用 if 和 for,这是非常实用的语法糖。
实例
void main() {
bool showAdmin = true;
// 集合中的 if:根据条件决定是否包含元素
var menuItems = [
'首页',
'教程',
if (showAdmin) '管理后台', // 条件为 true 时才加入
'关于',
];
print('菜单: $menuItems');
// 集合中的 for:从另一个集合生成元素
var numbers = [1, 2, 3];
var doubled = [
for (var n in numbers) n * 2, // 遍历生成新元素
];
print('翻倍: $doubled');
// if + for 可以组合使用
var tags = ['Dart', 'Flutter'];
var links = [
'RUNOOB 首页',
for (var tag in tags) 'https://runoob.com/$tag',
];
print('链接: $links');
}
bool showAdmin = true;
// 集合中的 if:根据条件决定是否包含元素
var menuItems = [
'首页',
'教程',
if (showAdmin) '管理后台', // 条件为 true 时才加入
'关于',
];
print('菜单: $menuItems');
// 集合中的 for:从另一个集合生成元素
var numbers = [1, 2, 3];
var doubled = [
for (var n in numbers) n * 2, // 遍历生成新元素
];
print('翻倍: $doubled');
// if + for 可以组合使用
var tags = ['Dart', 'Flutter'];
var links = [
'RUNOOB 首页',
for (var tag in tags) 'https://runoob.com/$tag',
];
print('链接: $links');
}
菜单: [首页, 教程, 管理后台, 关于] 翻倍: [2, 4, 6] 链接: [RUNOOB 首页, https://runoob.com/Dart, https://runoob.com/Flutter]
三种集合对比
| 特性 | List | Set | Map |
|---|---|---|---|
| 有序性 | 有序 | 无序 | 无序(但有迭代顺序) |
| 允许重复 | 允许 | 不允许 | 键不允许重复,值允许 |
| 索引访问 | 支持 [] | 不支持 | 通过键访问 [] |
| 典型场景 | 有序列表、序列 | 去重、标签集合 | 键值映射、配置项 |
| 创建字面量 | [] | {} | {key: value} |
初学者常犯的错误:Set 和 Map 的字面量都用 {}。区别在于:Map 的字面量中有冒号(key: value),而 Set 没有。如果写 var x = {},Dart 会推断为 Map,不是 Set。要创建空 Set,必须写 var x =
{}。
