Dart:集合-List、Map和Set

集合(Collection)

Dart 中的集合(Collection)是什么

集合是 存放一组数据的容器,是 Dart 核心库 dart:core 提供的基础数据结构

Dart 集合的特点:强类型(除非用 dynamic)、支持泛型、支持迭代

集合三大类

类型 描述 是否有序 是否允许重复元素 常见用途
List 有序集合,类似数组 ✅ 有序 ✅ 允许重复 顺序存储数据,如学生名单
Set 无序且唯一元素集合 ❌ 无序(Dart 默认 Set 是无序的,但 LinkedHashSet 可以有序) ❌ 不允许重复 存放唯一值,如标签列表
Map 键值对集合(key-value) ❌ key 无序(但 LinkedHashMap 保序) key 唯一,value 可重复 存储关联数据,如配置表

三者的关系

dart:core
  ├── Iterable<E>
  │     ├── List<E> (有序, 允许重复)
  │     └── Set<E>  (无序, 不重复)
  └── Map<K, V>     (键值对, key 唯一)

ListSetMap 都属于 Dart 集合类型(Collection) 的实现

它们并不是继承自一个统一的 Collection 基类(Dart 没有 Collection 类),而是分别实现了不同的接口:

  • List<E> 实现了 Iterable<E>
  • Set<E> 实现了 Iterable<E>
  • Map<K, V> 并不继承 Iterable,但可以通过 .entries 转成 Iterable<MapEntry<K, V>>

共同点

  1. 都支持泛型
  2. 都可用 for...inforEach 遍历(Map 需遍历 entrieskeysvalues
  3. 都是 dart:core 的核心数据结构

有序集合:List

什么是 List

List有序集合,可以通过索引访问(从 0 开始)

Dart 中的 List 相当于 Java 里的 ArrayList

允许存储重复元素

定义 List 的方式

// 1. 自动推断类型
var numbers = [1, 2, 3];

// 2. 显式指定泛型
List<int> ages = [20, 25, 30];

// 3. 空列表(可变)
var emptyList = <String>[];

// 4. 不可变 List(常量)
const fixedList = [1, 2, 3];

定长List

固定长度 ListList.filled):长度固定,不能 add/remove,但是可以改变里面的内容。

// 创建一个长度为 3 的 List,初始值为 0
// 第一个参数是长度,第二个参数是初始值
var l = List<int>.filled(3, 0);
l[0] = 1;
l[1] = 2;
l[2] = 3;
print(l); // [1, 2, 3]

不可变 Listconst):不能修改

const immutable = [1, 2, 3];

生成数据

基本语法

List.generate(
  int length,                  // 要生成的长度
  E generator(int index),      // 根据索引生成元素的函数
  { bool growable = true }     // 是否可变(默认 true)
)

length:生成的列表长度

generator:一个函数,接收当前索引 index,返回该位置的值,**必须返回(return)**一个值(对应当前索引的元素)

growable:是否允许后续 add()remove()(默认 true

// 生成一个 0, 1, 2, 3, 4 的列表
var list = List.generate(5, (index) => index);
print(list); // [0, 1, 2, 3, 4]

举例

// 生成 ["Item 0", "Item 1", ...]
var items = List.generate(5, (i) => 'Item $i');
print(items); // [Item 0, Item 1, Item 2, Item 3, Item 4]

上面的写法相当于下面:

var items = List.generate(5, (i) {
  return 'Item $i';
});
print(items); // [Item 0, Item 1, Item 2, Item 3, Item 4]

为什么可以省略 return

  • 规则:如果函数体是一个单一表达式,可以用 => 省略 {}return,表达式结果会自动返回
  • 这是 Dart 语言的简化语法,不是说不需要返回值,而是 返回值写在箭头右边

要生成一个包含时间戳、随机数和索引的列表:

import 'dart:math';

void main() {
  var random = Random();
  
  var list = List.generate(5, (index) {
    var timestamp = DateTime.now().millisecondsSinceEpoch;
    var rand = random.nextInt(100); // 0~99
    return "Index:$index Time:$timestamp Rand:$rand";
  });

  print(list); // [Index:0 Time:169... Rand:42, Index:1 Time:169... Rand:87, ...]
}

常用属性

属性 说明 示例
length 元素数量 list.length
first 第一个元素 list.first
last 最后一个元素 list.last
isEmpty 是否为空 list.isEmpty
isNotEmpty 是否非空 list.isNotEmpty
reversed 倒序(返回 Iterable) list.reversed.toList()

常用方法

方法 说明 示例
add() 追加元素 list.add(4)
addAll() 追加多个元素 list.addAll([5, 6])
insert(index, value) 指定位置插入 list.insert(1, 10)
insertAll(index, list) 插入多个 list.insertAll(2, [7, 8])
remove(value) 删除某值(只删第一个匹配) list.remove(2)
removeAt(index) 删除指定位置 list.removeAt(0)
removeLast() 删除最后一个 list.removeLast()
clear() 清空 list.clear()
contains(value) 是否包含 list.contains(3)
indexOf(value) 查找索引 list.indexOf(3)
indexWhere(test,start) 用来查找第一个满足条件的元素的 索引(位置),如果找不到满足条件的元素,返回 -1 list.indexWhere((item) => item.length > 5);
sort() 排序 list.sort()
var list = <int>[1, 2, 3];

// 常规写法(一步一步来)
list.add(4);
list.addAll([5, 6]);
list.insert(1, 10); // 在索引 1 插入 10
list.remove(2); // 删除值 2(只删第一个匹配)
list.sort(); // 排序

print(list); // [1, 3, 4, 5, 6, 10]

// 使用级联运算符 (..)
var anotherList = <int>[]
  ..add(7)
  ..addAll([8, 9])
  ..insert(0, 6)
  ..remove(9)
  ..sort();

print(anotherList); // [6, 7, 8]

级联运算符

级联运算符(Cascade Operator)是 Dart 语言里的一个特殊语法,写作 ..,用于对同一个对象连续调用多个方法或访问多个属性,代码更简洁、易读。

  • 允许你对同一个对象调用多个方法或属性赋值,而不需要重复写对象名
  • 即使被调用的方法返回 void,也能继续链式调用
var buffer = StringBuffer()
  ..write('Hello')
  ..write(' ')
  ..write('World')
  ..write('!');
print(buffer.toString()); // Hello World!

//======================等价于===============================

var buffer = StringBuffer();
buffer.write('Hello');
buffer.write(' ');
buffer.write('World');
buffer.write('!');
print(buffer.toString());

普通链式调用不能用,级联调用可以用

var list = <int>[];

// list.add(1).add(2); // ❌ 错误,add 返回 void,不能链式调用

list
  ..add(1)
  ..add(2)
  ..add(3);
print(list); // [1, 2, 3]

indexWhere

indexWhereListIterable 类中常用的方法,用来查找第一个满足条件的元素的 索引(位置),如果找不到满足条件的元素,返回 -1

方法签名:

int indexWhere(bool test(E element), [int start = 0])
  • test:一个返回 bool 的函数,传入当前元素,判断是否满足条件
  • start:可选参数,指定从哪个索引开始查找(默认从 0 开始)

使用示例:

void main() {
  var list = ['apple', 'banana', 'orange', 'pineapple'];

  // 找出第一个长度大于5的字符串的索引
  int index = list.indexWhere((item) => item.length > 5);

  print(index); // 1,"banana" 的长度是 6
}

找不到时返回 -1:

var list = ['a', 'b', 'c'];
var index = list.indexWhere((e) => e == 'z');
print(index); // -1,没有找到

从指定索引开始查找:

var list = ['a', 'b', 'c', 'b'];
var index = list.indexWhere((e) => e == 'b', 2);
print(index); // 3,从索引 2 开始找,找到索引 3

区间Range

fillRange

void fillRange(int start, int end, [E fillValue])
  • 将指定范围 [start, end) 内的元素全部替换成同一个新值。
  • start:起始索引(包含)
  • end:结束索引(不包含)
  • fillValue:填充值(默认 null
void main() {
  var list = [1, 2, 3, 4, 5];
  list.fillRange(1, 4, 0); // 把索引 1 到 3 的元素替换成 0
  print(list); // [1, 0, 0, 0, 5]
}

getRange

Iterable<E> getRange(int start, int end)
  • 获取指定范围的元素子列表,返回一个 Iterable,不修改原列表。
  • start:起始索引(包含)
  • end:结束索引(不包含)
void main() {
  var list = [10, 20, 30, 40, 50];
  var sub = list.getRange(1, 4);
  print(sub.toList()); // [20, 30, 40]
}

sublist

List<E> sublist(int start, [int? end])
  • sublistList 类的方法,用来 从原列表中截取指定范围的元素,返回一个新的列表(List)
  • start:起始索引(包含)
  • end:结束索引(不包含),可选,不写默认截取到末尾
void main() {
  var list = [10, 20, 30, 40, 50];
  var sub = list.sublist(1, 4);
  print(sub); // [20, 30, 40]
}

sublist 返回的是一个新的 List 实例,是原列表指定范围元素的独立拷贝。因此,修改原列表的元素不会改变新列表内容,反之亦然。

void main() {
  var list = [1, 2, 3, 4];
  var sub = list.sublist(1, 3); // [2, 3]

  list[1] = 99; 
  print(list); // [1, 99, 3, 4]
  print(sub);  // [2, 3],不变
}

相关方法

方法名 功能说明 备注
replaceRange 用另一个可迭代对象替换指定范围的元素 修改原列表
removeRange 删除指定范围内的元素 修改原列表
setRange 将指定范围内的元素替换为另一个列表的一部分 修改原列表
sublist 返回指定范围的子列表,返回的是新的 List 类似 getRange,但返回 List
void main() {
  var list = [1, 2, 3, 4, 5];

  // fillRange 替换元素
  list.fillRange(1, 3, 9);
  print(list); // [1, 9, 9, 4, 5]

  // getRange 获取子集合(Iterable)
  var range = list.getRange(1, 4);
  print(range.toList()); // [9, 9, 4]

  // replaceRange 替换范围
  list.replaceRange(2, 4, [7, 8]);
  print(list); // [1, 9, 7, 8, 5]

  // removeRange 删除范围元素
  list.removeRange(1, 3);
  print(list); // [1, 8, 5]

  // setRange 用另一个列表的元素替换指定范围
  list.setRange(0, 2, [0, 0]);
  print(list); // [0, 0, 5]

  // sublist 返回新 List(拷贝)
  var sublist = list.sublist(1, 3);
  print(sublist); // [0, 5]
}

排序

shuffle

void shuffle([Random? random])
  • shuffle 用来 随机打乱列表中元素的顺序
  • 它是 List 类的方法,直接修改原列表,不会返回新列表
  • 可以传入一个 Random 对象来自定义随机数源
  • 如果不传,默认使用 Dart 内部的随机数生成器
import 'dart:math';

void main() {
  var list = [1, 2, 3, 4, 5];
  // 打乱列表中的元素顺序,每次调用都会生成一个新的随机顺序
  // 因为使用了随机数生成器,所以每次运行结果可能不同
  list.shuffle();
  print(list); // 打乱后的顺序,例如 [3, 1, 5, 2, 4]

  // 用自定义随机数种子
  var rand = Random(42);
  list.shuffle(rand); // 使用相同的种子,生成相同的随机顺序
  print(list); // 总是生成相同的随机顺序,方便调试 [5, 3, 4, 2, 1]
}

sort

介绍

void sort([int compare(E a, E b)?])
  • sort 是 List 的一个方法,用于 对列表元素进行排序,直接修改原列表

  • 默认情况下,要求列表元素实现了 Comparable 接口(即支持比较大小)

  • 可选参数 compare 是一个比较函数,定义排序规则

    • 比较函数接收两个元素 ab,返回整数:
      • 负数:a 应该排在 b 前面
      • 0:两者相等
      • 正数:a 应该排在 b 后面
  • 如果不传比较函数,元素必须实现 Comparable接口,否则会报错

    • 内置类型(如 int, double, String 默认都实现了 Comparable,因此不传 compare 也能正常排序,按它们的自然顺序(数字大小、字母顺序)排序。

    • 自定义类 如果没有实现 Comparable,调用 sort() 不传 compare 会抛异常:

      Unhandled exception:
      type 'YourClass' is not a subtype of type 'Comparable'
      
    • 如果自定义类实现了 Comparable,就可以不传 compare,用类内定义的比较规则排序。


默认排序示例

void main() {
  var list = [5, 3, 8, 1];
  list.sort();
  print(list); // [1, 3, 5, 8]
}

默认是升序排序,因为 int 实现了 Comparable

自定义排序示例

// 比如按字符串长度排序:
var list = ['apple', 'banana', 'kiwi', 'pear'];
list.sort((a, b) => a.length.compareTo(b.length));
print(list); // [kiwi, pear, apple, banana]

复杂对象排序

class Person {
  String name;
  int age;
  Person(this.name, this.age);
}

void main() {
  var people = [
    Person('Alice', 30),
    Person('Bob', 25),
    Person('Charlie', 35),
  ];

  // 按年龄升序排序
  people.sort((a, b) => a.age.compareTo(b.age));

  for (var p in people) {
    print('${p.name}: ${p.age}');
  }
}

输出:

Bob: 25
Alice: 30
Charlie: 35

实现Comparable接口

var numbers = [3, 1, 4, 2];
numbers.sort();  // 正常,按数字升序排序
print(numbers);  // [1, 2, 3, 4]

class Person {
  String name;
  int age;
  Person(this.name, this.age);
}

var people = [
  Person('Alice', 30),
  Person('Bob', 25),
];

// people.sort(); // ❌ 报错,Person 没实现 Comparable

// 解决方案1:实现 Comparable
class Person2 implements Comparable<Person2> {
  String name;
  int age;
  Person2(this.name, this.age);

  @override
  int compareTo(Person2 other) => age.compareTo(other.age);
}

var people2 = [
  Person2('Alice', 30),
  Person2('Bob', 25),
];
people2.sort(); // 正常,按年龄排序

操作符

索引操作符 [][]=

读取元素

var list = [10, 20, 30];
print(list[1]); // 20,访问索引为1的元素

赋值元素

list[1] = 25;
print(list); // [10, 25, 30]

+ 操作符(连接两个列表)

作用是将两个列表连接成一个新列表,不修改原列表

var a = [1, 2];
var b = [3, 4];
var c = a + b;
print(c); // [1, 2, 3, 4]
print(a); // [1, 2],原列表不变
print(b); // [3, 4]

== 操作符(判断两个列表是否相等)

默认情况下,List== 是比较引用地址(是否是同一个对象)

要比较内容相等,可以用 ListEquality(来自 collection 包)

var a = [1, 2];
var b = [1, 2];
print(a == b); // false,引用不同

import 'package:collection/collection.dart';
var eq = ListEquality();
print(eq.equals(a, b)); // true,内容相同

遍历 List

var list = ['a', 'b', 'c'];

// for 循环
for (int i = 0; i < list.length; i++) {
  print(list[i]);
}

// for-in
for (var item in list) {
  print(item);
}

// forEach
list.forEach((item) => print(item));

键值对集合:Map

Map 在 Dart 中就是 键值对(key-value pair) 的集合,类似于 Java 的 HashMap,但 Dart 中的 Map 更加灵活。

定义 Map

字面量方式

var map1 = {
  'name': 'Alice',
  'age': 25,
};
// 空map
var map1 = {};

构造函数方式

var map2 = Map(); // 空 Map
map2['name'] = 'Bob';
map2['age'] = 30;

指定类型

var map3 = <String, int>{
  'score1': 90,
  'score2': 85,
};

和 Java 泛型类似,<String, int> 表示 Key 为 String,Value 为 int。

在 Dart 中,如果 Map 不指定类型,它会被推断为:Map<dynamic, dynamic>。也就是说,key 和 value 都是 dynamic,类型完全不受限制。

var map = {}; // Map<dynamic, dynamic>
map['name'] = 'Alice';
map[123] = true;
map[false] = 3.14;

print(map);
// 输出: {name: Alice, 123: true, false: 3.14}

常用属性

print(map1.length); // 键值对数量
print(map1.isEmpty); // 是否为空
print(map1.isNotEmpty); // 是否非空
print(map1.keys);   // 所有键 (Iterable)
print(map1.values); // 所有值 (Iterable)

常用方法

方法 作用
map[key] 访问 key 对应的 value,key 不存在则返回 null
map[key] = value 设置/更新 key 对应的值
containsKey(key) 是否包含某个 key
containsValue(value) 是否包含某个值
remove(key) 删除指定 key
removeWhere(condition) 删除所有满足条件的键值对。
clear() 清空 Map
forEach((k, v) => {}) 遍历键值对
putIfAbsent(key, () => value) 如果 key 不存在则赋值
addAll(otherMap) 合并另一个 Map
addEntries(MapEntry) 批量往 Map 里添加多个键值对(来自 Iterable<MapEntry<K, V>>)。如果 key 已存在,会直接覆盖原来的值。
update(key, (v) => newValue, ifAbsent: () => defaultValue) 更新 key 对应的值
updateAll((v) => newValue) Map 中的所有 value 进行批量更新,key 不变。
var map = <String, dynamic>{
  'name': 'Charlie',
  'age': 28
};

// 添加/修改
map['age'] = 29;

// 判断
print(map.containsKey('name')); // true

// 删除
map.remove('age');

// 遍历
map.forEach((key, value) {
  print('$key: $value');
});

// 如果 key 不存在才赋值
map.putIfAbsent('city', () => 'Beijing');
print(map); // {name: Charlie, city: Beijing}

// 合并 Map
map.addAll({'country': 'China', 'gender': 'Male'});

// 更新值
map.update('city', (value) => 'Shanghai');
map.update('zipcode', (value) => 10000, ifAbsent: () => 200000);

// 批量往 Map 里添加多个键值对
//适合把其他集合(List、Set 等)转成 MapEntry 再批量插入。
//避免多次 map[key] = value 的重复代码
var map2 = {'a': 1, 'b': 2};
map2.addEntries([
  MapEntry('c', 3),
  MapEntry('b', 20), // 覆盖 b 的值
]);
print(map2); // {a: 1, b: 20, c: 3}


// 删除所有符合条件的键值对:和 List.removeWhere 类似,只不过是对 Map。
// 参数是一个函数 (K key, V value) → bool,返回 true 的键值对会被移除。
var map3 = {'a': 1, 'b': 2, 'c': 3};
// 删除值大于 1 的键值对
map3.removeWhere((key, value) => value > 1);
print(map3); // {a: 1}


// 更新所有的键值对
var map4 = {'a': 1, 'b': 2, 'c': 3};
// 所有值 * 10
map4.updateAll((key, value) => value * 10);
print(map4); // {a: 10, b: 20, c: 30}

常用操作符

[](下标访问)

  • 读取某个 key 对应的 value
  • 写入某个 key 的 value(不存在会新增)
var map = {'a': 1, 'b': 2};

print(map['a']); // 1

map['c'] = 3; // 新增
print(map); // {a: 1, b: 2, c: 3}

==(相等比较)

判断两个 Map 是否相等(key 和 value 都相等,且顺序无关)。

var m1 = {'a': 1, 'b': 2};
var m2 = {'b': 2, 'a': 1};

print(m1 == m2); // true

无序集合:Set

创建Set

使用字面量创建

var numbers = {1, 2, 3};
print(numbers); // {1, 2, 3}

使用 {} 且没有键值对时,Dart 会推断为 Set

Dart 在用字面量创建 Set 时会根据你放进去的元素 自动推断类型

  • 元素类型一致 → 推断为该类型的 Set

    var names = {'Alice', 'Bob'}; // Set<String>
    
  • 元素类型不同 → 推断为 Set<Object>(或者更精确的公共父类型)

    var mixed = {1, 'two', 3.0};
    print(mixed.runtimeType); // 输出:_Set<Object>
    
  • 空字面量 {} → 默认推断为 Map,所以如果要创建空 Set 必须显式声明类型:

    var emptySet = <int>{}; // 空 Set<int>
    

构造函数

var emptySet = <int>{};       // 空 Set,指定类型
var stringSet = Set<String>(); // 另一种写法
// 如果不想指定类型
var stringSet = Set();
var a = <dynamic>{};

注意:

var s = {}; // 这是 Map,不是 Set

所以空 Set 要写成 <Type>{}

从 List 转为 Set

var list = [1, 2, 2, 3];
var uniqueSet = list.toSet();
print(uniqueSet); // {1, 2, 3} // 去重

常用属性

var numbers = {1, 2, 3};
print(numbers.length); // 元素个数
print(numbers.isEmpty); // 是否为空
print(numbers.isNotEmpty); // 是否非空
print(numbers.first); // 第一个元素
print(numbers.last); // 最后一个元素

常用方法

增加

方法 说明 示例
add(element) 添加单个元素(重复的会被忽略) s.add(4);
addAll(iterable) 批量添加元素 s.addAll([5, 6, 7]);

删除

方法 说明 示例
remove(element) 删除指定元素 s.remove(3);
removeAll(elements) 删除多个元素 s.removeAll({1, 2});
removeWhere(predicate) 按条件删除 s.removeWhere((e) => e.isOdd);
clear() 清空 Set s.clear();

查询

方法 说明 示例
contains(element) 判断是否包含元素 s.contains(2)
containsAll(elements) 是否包含所有给定元素 s.containsAll([1, 3])

遍历(Iterate)

for (var e in s) {
  print(e);
}

s.forEach((e) => print(e));

交并差(Set 运算)

Dart Set 内置了集合运算方法(返回新的 Set,不改变原 Set):

var a = {1, 2, 3};
var b = {3, 4, 5};

print(a.union(b));        // 并集 {1, 2, 3, 4, 5}
print(a.intersection(b)); // 交集 {3}
print(a.difference(b));   // 差集 {1, 2}

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1909773034@qq.com

×

喜欢就点赞,疼爱就打赏