Featured image of post Flutter/Dart第03天:Dart可迭代集合

Flutter/Dart第03天:Dart可迭代集合

摘要:在Dart学习的第02天,我们通过基础语法说明和样例代码的方式,学习了Dart的16个基础语法,这些基础语法给我们后面编写的Flutter程序打下来坚实基础。今天,我们继续深入学习Dart乃至所有编程语言都非常重要的部分:可迭代的集合……

Dart官网代码实验室:https://dart.dev/codelabs/iterables

重要说明:本博客基于Dart官网代码实验室,但并不是简单的对官网文章进行翻译,我会根据个人研发经验,在覆盖官网文章核心内容情况下,加入自己的一些扩展问题和问题演示和总结,包括名称解释、使用场景说明、代码样例覆盖等。

可迭代集合说明

什么是集合?集合代表一组对象的组合,集合中的对象一般称为元素,元素的数量可以是0个(即空集合),也可以有多个。

什么是迭代?迭代即顺序访问,即这个集合中的元素可从头到尾进行顺序访问(一般在循环遍历中使用)。在Java中,我们知道有个Iterable迭代类,在Dart中也有这个类(https://api.dart.dev/stable/3.1.3/dart-core/Iterable-class.html),我们用的最多的就是List和Set接口,他们是迭代集合的基础,也是一个应用程序的基础。

Map是可迭代集合吗?Map类代表了一组元素,因此它是一个集合。但Map类没有实现Iterable类(https://api.dart.dev/stable/3.1.3/dart-core/Map-class.html),因此它不可迭代,也就是说:Map是不可迭代的集合。但是它的元素集合(Map#entries)、键集合(Map#keys)和值集合(Map#values)都是可迭代集合。

迭代和集合访问元素的不同方式:迭代通过elementAt(index)方法访问元素,而集合可以通过[index]下标的方法访问元素:

1
2
3
4
5
6
7
8
void main() {
  // 1. 迭代和集合访问元素
  final List<int> alist = [1, 2, 3];
  final Iterable<int> iterable = alist;
  print('1. 迭代和集合访问元素: ${iterable.elementAt(2)} <-> ${alist[2]}');
  
  // 结果:1. 迭代和集合访问元素: 3 <-> 3
}

可迭代集合元素访问方法

可迭代集合元素的访问方式有很多种,包括for循环,集合的第1个元素,集合的最后1个元素,寻找符合条件的第1个元素等。

for-in循环访问集合元素

这种方式使用最多了,各种编程语言基本类似:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
void main() {
  final List<int> alist = [1, 2, 3];
  
  // 2.1. for循环访问集合元素
  for (final element in alist) {
    print('2.1. for循环访问集合元素: $element');
  }
  
  // 结果:
  // 2.1. for循环访问集合元素: 1
  // 2.1. for循环访问集合元素: 2
  // 2.1. for循环访问集合元素: 3
}

first第一个和last最后一个元素:空集合异常

通过first和last属性,可直接访问集合的第1个和最后1个元素:

1
2
3
4
5
6
7
8
void main() {
  final List<int> alist = [1, 2, 3];

  // 2.2 first第一个和last最后一个元素
  print('2.2. first第一个和last最后一个元素: first=${alist.first}, last=${alist.last}');
  
  // 结果:2.2. first第一个和last最后一个元素: first=1, last=3
}

扩展问题:如果集合只有1个元素,或者集合是空集合,first和last返回的内容是什么呢?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
void main() {
  final List<int> oneList = [1];
  print('2.2. first第一个和last最后一个元素: one.first=${oneList.first}, one.last=${oneList.last}');
  
  // 结果:2.2. first第一个和last最后一个元素: one.first=1, one.last=1
  
  final List<int> emptyList = [];
  print('2.2. first第一个和last最后一个元素: empty.first=${emptyList.first}, empty.last=${emptyList.last}');
  
  // 结果:Bad state: No element
}

// 异常如下:
Unhandled exception:
Bad state: No element
#0      List.first (dart:core-patch/growable_array.dart:343:5)

结论:只有1个元素的集合,first和last返回值相同,均为唯一的那个元素;对于空集合,first或者last均抛出异常!

firstWhere()/orElse符合条件的第1个元素

断言:一个返回true/false的表达式、方法或者代码块。firstWhere()的本质就是遍历集合,对每个元素进行断言,然后返回第一个断言为true的元素。

1
2
3
4
5
6
7
8
9
void main() {
  final List<int> alist = [1, 2, 3];

  // 2.3. firstWhere()符合条件的第1个元素
  final int firstWhere = alist.firstWhere((element) => element > 1);
  print('2.3. firstWhere()符合条件的第1个元素: $firstWhere');

  // 结果:2.3. firstWhere()符合条件的第1个元素: 2
}

扩展问题:如果过滤条件均不满足(即每个元素断言均返回false),则返回结果是什么呢?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
void main() {
  final List<int> alist = [1, 2, 3];

  // 2.3. firstWhere()符合条件的第1个元素
  final int firstWhere2 = alist.firstWhere((element) => element > 3);
  print('2.3. firstWhere()符合条件的第1个元素2: $firstWhere2');

  // 结果:Bad state: No element
}

// 异常如下:
Unhandled exception:
Bad state: No element
#0      ListBase.firstWhere (dart:collection/list.dart:132:5)

结论:和空集合一样,当firstWhere()无法匹配到任何元素时,会抛出异常!

那么,当无法匹配到任何元素时,有没有办法不抛出异常,而是返回一个默认值呢?

答案是有的:firstWhere()断言之后,增加orElse默认值的命名参数,它是一个函数!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
void main() {
  final List<int> alist = [1, 2, 3];

  // 2.3. firstWhere()符合条件的第1个元素
  final int firstWhere2 = alist.firstWhere(
    (element) => element > 3,
    orElse: () => -1,
  );
  print('2.3. firstWhere()符合条件的第1个元素2: $firstWhere2');

  // 结果:2.3. firstWhere()符合条件的第1个元素2: -1
}

any()/every()集合检测(有趣的结果和源代码)

当我们需要检测集合中是否存在符合某个条件的元素,或者所有元素是否符合某个条件。在Dart语言中,我们可以使用any()和every()这两个集合条件检测方法,来达到我们的目的。

  • any()方法:集合中存在任一一个元素符合条件
  • every()方法:集合中的所有元素均符合条件
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
void main() {
  final List<int> alist = [1, 2, 3];

  // 2.4. any()/every()集合条件检测
  final bool anyGtTwo = alist.any((element) => element > 2);
  final bool everyGtZero = alist.every((element) => element > 0);
  final bool everyGtTwo = alist.every((element) => element > 2);
  print('2.4. any()/every()集合条件检测: anyGtTwo=$anyGtTwo, everyGtZero=$everyGtZero, everyGtTwo=$everyGtTwo');

  // 结果:2.4. any()/every()集合条件检测: anyGtTwo=true, everyGtZero=true, everyGtTwo=false
}

扩展问题:如果是个空集合,any()和every()的结果如何,会抛出异常吗?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
void main() {
  final elist = <int>[];
  final bool anyGtZero = elist.any((element) => element > 0);
  final bool anyLtEqZero = elist.any((element) => element <= 0);
  print('2.4. any()-空集合条件检测: anyGtZero=$anyGtZero, anyLtEqZero=$anyLtEqZero');

  // 结果:2.4. any()-空集合条件检测: anyGtZero=false, anyLtEqZero=false

  final bool evyGtZero = elist.every((element) => element > 0);
  final bool evyLtEqZero = elist.every((element) => element <= 0);
  print('2.4. every()-空集合条件检测: evyGtZero=$evyGtZero, evyLtEqZero=$evyLtEqZero');

  // 结果:2.4. every()-空集合条件检测: evyGtZero=true, evyLtEqZero=true
}

有趣的结论:

  1. 针对空集合,any()和every()这2个集合条件检测方法,不会抛出异常!
  2. 针对空集合,any()不论断言结果如何,返回值均为false(这个结果比较容易理解)
  3. 针对空集合,every()不论断言结果如何,返回值均为true(这个结果有的奇怪!)

我们打开源代码,看看any()和every()的发现:any()的默认返回值为false,every()的默认返回值true

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
abstract mixin class Iterable<E> {
  // 默认返回值:false
  bool any(bool test(E element)) {
    for (E element in this) {
      if (test(element)) return true;
    }
    return false;
  }

  // 默认返回值:true
  bool every(bool test(E element)) {
    for (E element in this) {
      if (!test(element)) return false;
    }
    return true;
  }
}

where()/takeWhile()/skipWhile()筛选子集合

上面的any()/every()只是一个条件判断,本节来看看,通过条件来筛选子集合:

  • where()方法:遍历集合每个元素,返回所有符合条件的子集合;若所有元素均不符合条件,则返回空集合,而不是抛出异常!
  • takeWhile()方法:遍历集合元素,构成新子集合元素,直到不符合条件的元素为止。
  • skipWhile()方法:与takeWhile()相反,遍历集合元素,过滤掉前面所有符合条件元素,取结合后面元素。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
void main() {
  final List<int> alist = [1, 2, 3];
  
  // 2.5. where()/takeWhile()/skipWhile()筛选子集合
  final gtOneList = alist.where((element) => element > 1);
  print('2.5. where()-筛选子集合: gtOneList=$gtOneList');

  // 结果:2.5. where()-筛选子集合: gtOneList=(2, 3)

  final takeLtThreeList = alist.takeWhile((element) => element < 3);
  print('2.5. takeWhile()-筛选子集合: takeLtTwoList=$takeLtThreeList');

  // 结果:2.5. takeWhile()-筛选子集合: takeLtTwoList=(1, 2)

  final skipNeqTwoList = alist.skipWhile((element) => element != 2);
  print('2.5. skipWhile()-筛选子集合: skipNeqTwoList=$skipNeqTwoList');

  // 结果:2.5. skipWhile()-筛选子集合: skipNeqTwoList=(2, 3)
}

map()转换集合元素

对集合的每个元素,通过map()函数进行一次计算,可把一个结合转换为另一个元素的集合。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
void main() {
  final List<int> alist = [1, 2, 3];

  // 2.6. map()转换集合元素
  final alistPlus3 = alist.map((e) => 3 + e);
  print('2.6. map()转换集合元素: $alist -> $alistPlus3');

  // 结果:2.6. map()转换集合元素: [1, 2, 3] -> (4, 5, 6)

  final intToStringList = alist.map((e) => 'value:$e');
  print('2.6. map()转换集合元素: $alist -> $intToStringList');

  // 结果:2.6. map()转换集合元素: [1, 2, 3] -> (value:1, value:2, value:3)
}

最后总结

特别注意:

  1. first/last/firstWhere()这些返回值为单个元素方法,当为空集合或者无法找到元素时,会抛出异常。
  2. where()/takeWhile()/skipWhile()这些返回值为集合的方法,当为空集合或者无法匹配到元素,返回空集合,不会抛出异常。
  3. any()默认返回值为false,every()默认返回值为true

我的本博客原地址:https://ntopic.cn/p/2023092701/


邮箱:obullxl@qq.com
QQ:303630027(老牛啊)
微信:imxulin(奔跑的蜗牛)
支付宝:obullxl@163.com