2.1 Dart中的集合:List, Set, Map

基础知识

集合是用于存储和组织数据的重要数据结构。Dart 提供了几种内置的集合类型,包括 List(列表)、Set(集合)和 Map(映射),它们各自适用于不同的数据存储和访问场景。

1. List (列表)

List 是一个有序的、可索引的元素集合,类似于其他编程语言中的数组。List 中的元素可以重复。

  • 创建 List

    dart
    复制代码
    // 字面量创建
    List<int> numbers = [1, 2, 3, 4, 5];
    List<String> fruits = ["apple", "banana", "orange"];
    
    // 使用 List 构造函数
    List<double> prices = List.filled(3, 0.0); // 创建一个包含3个0.0的固定长度列表
    List<dynamic> mixedList = [1, "hello", true]; // 动态类型列表
  • 常用操作

    • 访问元素:通过索引访问,索引从 0 开始。
      dart
      复制代码
      print(numbers[0]); // 输出: 1
      print(fruits[1]); // 输出: banana
    • 添加元素add()addAll()insert()
      dart
      复制代码
      fruits.add("grape"); // ["apple", "banana", "orange", "grape"]
      fruits.addAll(["kiwi", "mango"]); // ["apple", ..., "mango"]
      fruits.insert(1, "lemon"); // ["apple", "lemon", "banana", ...]
    • 删除元素remove()removeAt()removeLast()clear()
      dart
      复制代码
      fruits.remove("lemon");
      fruits.removeAt(0); // 删除第一个元素
      fruits.removeLast();
      fruits.clear(); // 清空列表
    • 修改元素:通过索引赋值。
      dart
      复制代码
      numbers[0] = 10; // numbers 现在是 [10, 2, 3, 4, 5]
    • 长度length 属性。
      dart
      复制代码
      print(numbers.length); // 输出: 5
    • 遍历for 循环、for-in 循环、forEach()
      dart
      复制代码
      for (int i = 0; i < fruits.length; i++) {
        print(fruits[i]);
      }
      for (String fruit in fruits) {
        print(fruit);
      }
      fruits.forEach((fruit) => print(fruit));

2. Set (集合)

Set 是一个无序的、唯一的元素集合。Set 中的元素不能重复,如果尝试添加重复元素,Set 会忽略该操作。

  • 创建 Set

    dart
    复制代码
    // 字面量创建
    Set<int> uniqueNumbers = {1, 2, 3, 1}; // 实际存储为 {1, 2, 3}
    Set<String> colors = {"red", "green", "blue"};
    
    // 使用 Set 构造函数
    Set<String> emptySet = {}; // 注意:{} 默认是 Map,要明确指定类型
    Set<String> anotherEmptySet = Set<String>();
  • 常用操作

    • 添加元素add()addAll()
      dart
      复制代码
      uniqueNumbers.add(4); // {1, 2, 3, 4}
      uniqueNumbers.add(2); // 不会添加,因为 2 已存在
      colors.addAll({"yellow", "red"}); // "red" 不会重复添加
    • 删除元素remove()clear()
      dart
      复制代码
      colors.remove("green");
      colors.clear();
    • 检查元素contains()
      dart
      复制代码
      print(uniqueNumbers.contains(3)); // 输出: true
    • 集合操作union() (并集)、intersection() (交集)、difference() (差集)。
      dart
      复制代码
      Set<int> setA = {1, 2, 3};
      Set<int> setB = {3, 4, 5};
      print(setA.union(setB));       // {1, 2, 3, 4, 5}
      print(setA.intersection(setB)); // {3}
      print(setA.difference(setB));  // {1, 2}

3. Map (映射)

Map 是一个键值对的集合,其中每个键都是唯一的,并且映射到一个值。Map 类似于其他语言中的字典或哈希表。

  • 创建 Map

    dart
    复制代码
    // 字面量创建
    Map<String, String> capitals = {
      "USA": "Washington D.C.",
      "Japan": "Tokyo",
      "China": "Beijing"
    };
    Map<String, dynamic> userInfo = {
      "name": "Alice",
      "age": 30,
      "isActive": true
    };
    
    // 使用 Map 构造函数
    Map<int, String> emptyMap = {}; // 注意:{} 默认是 Map,但最好明确指定类型
    Map<int, String> anotherEmptyMap = Map<int, String>();
  • 常用操作

    • 访问元素:通过键访问值。
      dart
      复制代码
      print(capitals["USA"]); // 输出: Washington D.C.
      print(userInfo["age"]); // 输出: 30
    • 添加/修改元素:通过键赋值。
      dart
      复制代码
      capitals["France"] = "Paris"; // 添加新键值对
      capitals["USA"] = "New York"; // 修改已有键的值
    • 删除元素remove()
      dart
      复制代码
      capitals.remove("Japan");
    • 检查键/值containsKey()containsValue()
      dart
      复制代码
      print(capitals.containsKey("China")); // 输出: true
      print(capitals.containsValue("Tokyo")); // 输出: false (因为 Japan 已经被删除了)
    • 遍历forEach()keysvalues
      dart
      复制代码
      capitals.forEach((key, value) {
        print("$key: $value");
      });
      for (String key in capitals.keys) {
        print("Key: $key");
      }
      for (String value in capitals.values) {
        print("Value: $value");
      }

官方文档链接

Flutter 开发中的应用案例

集合在 Flutter 开发中无处不在,它们是组织和管理 UI 数据、状态以及从后端获取数据的基础。无论是构建列表、管理用户偏好设置,还是处理复杂的 JSON 数据,都离不开对 List、Set 和 Map 的熟练运用。

案例:一个简单的购物清单应用

我们将创建一个简单的购物清单应用,演示如何使用 List 来存储商品,以及如何使用 Map 来表示商品的属性。这个案例将涵盖 List 的添加、删除、修改和遍历操作。

dart
复制代码
import 'package:flutter/material.dart';

// 定义一个商品类,使用 Map 来表示其属性
class ShoppingItem {
  String name;
  int quantity;
  bool isPurchased;

  ShoppingItem({
    required this.name,
    this.quantity = 1,
    this.isPurchased = false,
  });

  // 将 ShoppingItem 转换为 Map,方便存储或传输
  Map<String, dynamic> toMap() {
    return {
      'name': name,
      'quantity': quantity,
      'isPurchased': isPurchased,
    };
  }

  // 从 Map 创建 ShoppingItem 对象
  factory ShoppingItem.fromMap(Map<String, dynamic> map) {
    return ShoppingItem(
      name: map['name'] as String,
      quantity: map['quantity'] as int,
      isPurchased: map['isPurchased'] as bool,
    );
  }
}

class ShoppingListScreen extends StatefulWidget {
  const ShoppingListScreen({super.key});

  @override
  State<ShoppingListScreen> createState() => _ShoppingListScreenState();
}

class _ShoppingListScreenState extends State<ShoppingListScreen> {
  // 使用 List 来存储 ShoppingItem 对象
  final List<ShoppingItem> _shoppingList = [
    ShoppingItem(name: '牛奶', quantity: 2),
    ShoppingItem(name: '面包', quantity: 1, isPurchased: true),
    ShoppingItem(name: '鸡蛋', quantity: 12),
  ];

  final TextEditingController _itemController = TextEditingController();

  void _addItem() {
    if (_itemController.text.isNotEmpty) {
      setState(() {
        _shoppingList.add(ShoppingItem(name: _itemController.text));
        _itemController.clear();
      });
    }
  }

  void _togglePurchase(int index) {
    setState(() {
      _shoppingList[index].isPurchased = !_shoppingList[index].isPurchased;
    });
  }

  void _deleteItem(int index) {
    setState(() {
      _shoppingList.removeAt(index);
    });
  }

  @override
  void dispose() {
    _itemController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('购物清单'),
      ),
      body: Column(
        children: <Widget>[
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: Row(
              children: <Widget>[
                Expanded(
                  child: TextField(
                    controller: _itemController,
                    decoration: const InputDecoration(
                      labelText: '添加新商品',
                      border: OutlineInputBorder(),
                    ),
                    onSubmitted: (_) => _addItem(), // 按回车键添加
                  ),
                ),
                const SizedBox(width: 8.0),
                ElevatedButton(
                  onPressed: _addItem,
                  child: const Text('添加'),
                ),
              ],
            ),
          ),
          Expanded(
            child: ListView.builder(
              itemCount: _shoppingList.length,
              itemBuilder: (context, index) {
                final item = _shoppingList[index];
                return Card(
                  margin: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
                  child: ListTile(
                    leading: Checkbox(
                      value: item.isPurchased,
                      onChanged: (bool? newValue) {
                        _togglePurchase(index);
                      },
                    ),
                    title: Text(
                      item.name,
                      style: TextStyle(
                        decoration: item.isPurchased ? TextDecoration.lineThrough : null,
                        color: item.isPurchased ? Colors.grey : Colors.black,
                      ),
                    ),
                    subtitle: Text('数量: ${item.quantity}'),
                    trailing: IconButton(
                      icon: const Icon(Icons.delete),
                      onPressed: () => _deleteItem(index),
                    ),
                  ),
                );
              },
            ),
          ),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // 示例:将所有商品转换为 Map 列表
          List<Map<String, dynamic>> itemsAsMaps = _shoppingList.map((item) => item.toMap()).toList();
          print('所有商品 (Map 形式): $itemsAsMaps');

          // 示例:从 Map 列表重新创建 ShoppingItem 对象
          List<ShoppingItem> recreatedItems = itemsAsMaps.map((map) => ShoppingItem.fromMap(map)).toList();
          print('重新创建的商品: ${recreatedItems.map((e) => e.name).toList()}');
        },
        child: const Icon(Icons.info_outline),
      ),
    );
  }
}

void main() {
  runApp(const MaterialApp(home: ShoppingListScreen()));
}

案例分析:

  • final List<ShoppingItem> _shoppingList = [...]:我们使用 List<ShoppingItem> 来存储购物清单中的所有商品。这是一个泛型 List,明确指定了列表中存储的元素类型是 ShoppingItem 对象。
  • ShoppingItem 类中的 toMap()fromMap():为了演示 Map 的应用,ShoppingItem 类提供了 toMap() 方法将对象转换为 Map<String, dynamic>,以及 factory ShoppingItem.fromMap() 工厂构造函数从 Map 创建对象。这在处理 JSON 数据(通常是 Map 结构)时非常有用。
  • _shoppingList.add(...):在 _addItem 方法中,我们使用 add() 方法向 _shoppingList 中添加新的商品。setState() 用于通知 Flutter 框架数据已更改,需要重新构建 UI。
  • _shoppingList[index].isPurchased = ...:在 _togglePurchase 方法中,我们通过索引访问 List 中的元素,并修改其 isPurchased 属性。这展示了 List 元素的修改操作。
  • _shoppingList.removeAt(index):在 _deleteItem 方法中,我们使用 removeAt() 方法根据索引删除 List 中的元素。
  • ListView.builder:Flutter 中构建动态列表的常用 Widget。它通过 itemCount 属性获取列表长度,并通过 itemBuilder 回调函数为每个列表项构建 UI。itemBuilder 内部通过 _shoppingList[index] 访问当前项的数据。
  • _shoppingList.map((item) => item.toMap()).toList():在 FloatingActionButtononPressed 回调中,我们演示了如何使用 map() 方法将 ShoppingItem 对象的 List 转换为 Map 对象的 List。这在数据序列化(例如保存到本地存储或发送到服务器)时非常有用。

这个案例全面展示了 Dart 中 List 和 Map 集合类型在 Flutter 应用中的实际应用。通过这些集合,我们可以高效地组织和管理数据,从而构建出功能丰富且交互性强的应用程序。