跳转到正文
YGJoon 技术博客

Dart 集合类型详解

发布于:
·1.3k 字 · 约 3 分钟·作者:
List、Set、Map 三大集合的创建与常用操作对照着学,附完整示例,日常数据处理查这一篇就够了。
Dart 集合类型详解文章封面
目录

List(列表)

列表创建

// 字面量创建
var numbers = [1, 2, 3, 4, 5];
List<String> fruits = ['apple', 'banana', 'orange'];

// 构造函数创建
var fixedList = List<int>.filled(5, 0); // 固定长度列表
var growableList = List<int>.empty(growable: true); // 可增长列表
var generatedList = List<int>.generate(5, (i) => i * i); // 生成列表

列表操作

var list = [1, 2, 3];

// 添加元素
list.add(4);
list.addAll([5, 6]);

// 删除元素
list.remove(1);
list.removeAt(0);
list.removeLast();

// 查找元素
int index = list.indexOf(3);
bool contains = list.contains(2);

// 修改元素
list[0] = 10;

// 遍历列表
for (var item in list) {
  print(item);
}

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

Set(集合)

集合创建

// 字面量创建
var numbers = {1, 2, 3, 4, 5};
Set<String> fruits = {'apple', 'banana', 'orange'};

// 构造函数创建
var numberSet = Set<int>();
numberSet.addAll([1, 2, 3]);

集合操作

var set1 = {1, 2, 3};
var set2 = {3, 4, 5};

// 添加和删除
set1.add(4);
set1.remove(1);

// 集合运算
var union = set1.union(set2);         // 并集
var intersection = set1.intersection(set2); // 交集
var difference = set1.difference(set2);     // 差集

// 检查包含关系
bool contains = set1.contains(2);
bool subset = set2.containsAll(set1); // set1 是否为 set2 的子集(Set 没有 isSubsetOf,用 containsAll 反着查)

Map(映射)

映射创建

// 字面量创建
var scores = {
  'math': 90,
  'history': 85,
  'science': 95,
};

Map<String, int> ages = {
  'John': 20,
  'Alice': 25,
  'Bob': 30,
};

// 构造函数创建
var map = Map<String, int>();
map['one'] = 1;
map['two'] = 2;

映射操作

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

// 添加和更新
map['c'] = 3;
map.putIfAbsent('d', () => 4);

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

// 访问元素
int? value = map['b'];
bool containsKey = map.containsKey('c');

// 遍历映射
map.forEach((key, value) => print('$key: $value'));

for (var entry in map.entries) {
  print('${entry.key}: ${entry.value}');
}

完整示例

class CollectionDemo {
  // List示例
  void listExample() {
    var fruits = ['apple', 'banana', 'orange'];
    
    // 基本操作
    fruits.add('grape');
    fruits.insert(1, 'mango');
    fruits.removeAt(0);
    
    // 变换操作
    var upperFruits = fruits.map((f) => f.toUpperCase()).toList();
    var longFruits = fruits.where((f) => f.length > 5).toList();
    
    // 排序
    fruits.sort((a, b) => a.compareTo(b));
    
    print('Fruits: $fruits');
    print('Upper fruits: $upperFruits');
    print('Long fruits: $longFruits');
  }
  
  // Set示例
  void setExample() {
    var numbers1 = {1, 2, 3, 4, 5};
    var numbers2 = {4, 5, 6, 7, 8};
    
    // 集合运算
    var union = numbers1.union(numbers2);
    var intersection = numbers1.intersection(numbers2);
    var difference = numbers1.difference(numbers2);
    
    print('Union: $union');
    print('Intersection: $intersection');
    print('Difference: $difference');
  }
  
  // Map示例
  void mapExample() {
    var students = {
      'John': {'age': 20, 'grade': 'A'},
      'Alice': {'age': 22, 'grade': 'B'},
      'Bob': {'age': 21, 'grade': 'A'},
    };
    
    // 基本操作
    students['Charlie'] = {'age': 23, 'grade': 'B'};
    students.remove('John');
    
    // 过滤和转换
    var gradeA = students.entries
        .where((entry) => entry.value['grade'] == 'A')
        .map((entry) => entry.key)
        .toList();
    
    print('Grade A students: $gradeA');
    
    // 遍历
    students.forEach((name, info) {
      print('$name: Age ${info['age']}, Grade ${info['grade']}');
    });
  }
}

void main() {
  var demo = CollectionDemo();
  
  print('=== List Example ===');
  demo.listExample();
  
  print('\n=== Set Example ===');
  demo.setExample();
  
  print('\n=== Map Example ===');
  demo.mapExample();
}

集合操作技巧

列表转换和过滤

var numbers = [1, 2, 3, 4, 5];

// 映射转换
var doubled = numbers.map((n) => n * 2).toList();

// 过滤
var evenNumbers = numbers.where((n) => n.isEven).toList();

// 归约
var sum = numbers.reduce((a, b) => a + b);

集合排序

var items = [3, 1, 4, 1, 5, 9, 2, 6, 5];

// 简单排序
items.sort();

// 自定义排序
items.sort((a, b) => b.compareTo(a)); // 降序

// 对象排序:a['age'] 取出来是 Object?,必须 as int 才能调 compareTo,否则编译不过
var people = [
  {'name': 'Bob', 'age': 20},
  {'name': 'Alice', 'age': 25},
];
people.sort((a, b) => (a['age'] as int).compareTo(b['age'] as int));

注意事项

  1. 使用类型安全的集合
  2. 注意不可变集合的使用
  3. 合理处理空值
  4. 避免过度嵌套
  5. 注意集合的性能影响

最佳实践

  1. 优先使用不可变集合
  2. 合理使用泛型
  3. 使用集合方法而不是循环
  4. 注意内存使用
  5. 适当使用懒加载集合

实战案例:遍历 Map 生成一个类

我的 ygking_json_to_dart_model 整个核心就是一次 Map 遍历(lib/services/json_converter.dart):

void _generateClass(Map<String, dynamic> map, ...) {
  // 生成字段声明
  map.forEach((key, value) {
    final type = _getFieldType(value, key);
    buffer.writeln('  $type? $key;');
  });
  // 生成构造函数
  buffer.writeln('  $className({');
  map.forEach((key, _) {
    buffer.writeln('    this.$key,');
  });
  // 生成 fromJson —— 又一次遍历
  map.forEach((key, value) { ... });
}

同一个 Map<String, dynamic> 被遍历了三次:字段声明、构造函数、fromJson。为什么不用一次遍历干完三件事?因为生成的代码有顺序要求:Dart 类里字段声明必须在构造函数前面,fromJson 必须在最后。三次遍历换来的是生成逻辑的清晰——每次遍历只做一件事。

这里有个集合选型的细节:json.decode 返回的是 Map<String, dynamic>,key 是 String,value 是 dynamic。dynamic 意味着”我不知道这是什么类型”,所以 _getFieldType 里全是 is 判断(value is List、value is Map)。如果当初把 value 定成 Object?,每次用之前都要 as 强转,更啰嗦。Map<String, dynamic> 是处理 JSON 的标准姿势,不是因为它”灵活”,而是因为 JSON 的 value 类型本来就是不确定的——类型系统要诚实地反映现实。

另外注意 map.forEach((key, _) 里的 _:Dart 里用下划线表示”我知道有这个参数但我不用”。这是个小约定,但 review 代码时一眼就能看出”这里故意忽略了 value”。

总结

List/Set/Map 的 API 看着多,常用的也就增删改查加 map/where 两板斧——上面实战案例里,我的 JSON 生成器靠一个 Map.forEach 就撑起了全部逻辑。集合选型的核心问题只有一个:你的数据有没有重复、顺不顺序、需不需要 key,答完这三个,选哪个集合自然就定了。


分享这篇文章:


相关文章

下一篇
Dart 错误处理详解