简述:
在上一篇文章中,我们全面地分析了常用集合的使用以及集合部分源码的分析。那么这一节讲点更实用的内容,绝对可以提高你的Flutter开发效率的函数,那就是集合中常用的操作符函数。这次说的内容的比较简单就是怎么用,以及源码内部是怎么实现的。
一、Iterable<E>
在dart中几乎所有集合拥有的操作符函数(例如: map、every、where、reduce等)都是因为继承或者实现了Iterable
。
1、Iterable类关系图
2、Iterable类方法图
二、forEach
1、介绍
void forEach(void f(E element))
forEach在dart中用于遍历和迭代集合,也是dart中操作集合最常用的方法之一。接收一个f(E element)
函数作为参数,返回值类型为空void.
2、使用方式
main() {
var languages = <String>['Dart', 'Kotlin', 'Java', 'Javascript', 'Go', 'Python', 'Swift'];
languages.forEach((language) => print('The language is $language'));//由于只有一个表达式,所以可以直接使用箭头函数。
languages.forEach((language){
if(language == 'Dart' || language == 'Kotlin') {
print('My favorite language is $language');
}
});
}
3、源码解析
void forEach(void f(E element)) {
//可以看到在forEach内部实际上就是利用for-in迭代,每迭代一次就执行一次f函数,
//并把当前element回调出去
for (E element in this) f(element);
}
三、map
1、介绍
Iterable<T> map<T>(T f(E e))
map函数主要用于集合中元素的映射,也可以映射转化成其他类型的元素。可以看到map接收一个T f(E e)
函数作为参数,最后返回一个泛型参数为T
的Iterable
。实际上是返回了带有元素的一个新的惰性Iterable
, 然后通过迭代的时候,对每个元素都调用f
函数。注意: f
函数是一个接收泛型参数为E
的元素,然后返回一个泛型参数为T
的元素,这就是map可以将原集合中每个元素映射成其他类型元素的原因。
2、使用方式
main() {
var languages = <String>['Dart', 'Kotlin', 'Java', 'Javascript', 'Go', 'Python', 'Swift'];
print(languages.map((language) => 'develop language is ${language}').join('---'));
}
3、源码解析
以上面的例子为例,
- 1、首先,需要明确一点,
languages
内部本质是一个_GrowableList<T>
, 我们都知道_GrowableList<T>
是继承了ListBase<T>
,然后ListBase<E>
又mixin withListMixin<E>
.所以languages.map
函数调用就是调用ListMixin<E>
中的map函数,实际上还是相当于调用了自身的成员函数map.
@pragma("vm:entry-point")
class _GrowableList<T> extends ListBase<T> {//_GrowableList<T>是继承了ListBase<T>
...
}
abstract class ListBase<E> extends Object with ListMixin<E> {//ListBase mixin with ListMixin<E>
...
}
- 2、然后可以看到
ListMixin<E>
实际上实现了List<E>
,然后List<E>
继承了EfficientLengthIterable<E>
,最后EfficientLengthIterable<E>
继承Iterable<E>
,所以最终的map函数来自于Iterable<E>
但是具体的实现定义在ListMinxin<E>
中。
abstract class ListMixin<E> implements List<E> {
...
//可以看到这里是直接返回一个MappedListIterable,它是一个惰性Iterable
Iterable<T> map<T>(T f(E element)) => MappedListIterable<E, T>(this, f);
...
}
- 3、为什么是惰性的呢,可以看到它并不是直接返回转化后的集合,而是返回一个带有值的MappedListIterable的,如果不执行
elementAt
方法,是不会触发执行map传入的f
函数, 所以它是惰性的。
class MappedListIterable<S, T> extends ListIterable<T> {
final Iterable<S> _source;//_source存储了所携带的原集合
final _Transformation<S, T> _f;//_f函数存储了map函数传入的闭包,
MappedListIterable(this._source, this._f);
int get length => _source.length;
//注意: 只有elementAt函数执行的时候,才会触发执行_f方法,然后通过_source的elementAt函数取得原集合中的元素,
//最后针对_source中的每个元素执行_f函数处理。
T elementAt(int index) => _f(_source.elementAt(index));
}
- 4、一般不会单独使用map函数,因为单独使用map的函数时,仅仅返回的是惰性的
MappedListIterable
。由上面的源码可知,仅仅在elementAt调用的时候才会触发map中的闭包。所以我们一般使用完map后会配合toList()、toSet()
函数或者触发elementAt
函数的函数(例如这里的join
)一起使用。
languages.map((language) => 'develop language is ${language}').toList();//toList()方法调用才会真正去执行map中的闭包。
languages.map((language) => 'develop language is ${language}').toSet();//toSet()方法调用才会真正去执行map中的闭包。
languages.map((language) => 'develop language is ${language}').join('---');//join()方法调用才会真正去执行map中的闭包。
List<E> toList({bool growable = true}) {
List<E> result;
if (growable) {
result = <E>[]..length = length;
} else {
result = List<E>(length);
}
for (int i = 0; i < length; i++) {
result[i] = this[i];//注意: 这里的this[i]实际上是运算符重载了[],最终就是调用了elementAt函数,这里才会真正的触发map中的闭包,
}
return result;
}
四、any
1、介绍
bool any(bool test(E element))
any函数主要用于检查是否存在任意一个满足条件的元素,只要匹配到第一个就返回true, 如果遍历所有元素都不符合才返回false.
any函数接收一个bool test(E element)
函数作为参数,test
函数回调一个E
类型的element
并返回一个bool
类型的值。
2、使用方式
main() {
bool isDartExisted = languages.any((language) => language == 'Dart');
}
3、源码解析
bool any(bool test(E element)) {
int length = this.length;//获取到原集合的length
//遍历原集合,只要找到符合test函数的条件,就返回true
for (int i = 0; i < length; i++) {
if (test(this[i])) return true;
if (length != this.length) {
throw ConcurrentModificationError(this);
}
}
//遍历完集合后,未找到符合条件的集合就返回false
return false;
}
五、every
1、介绍
bool every(bool test(E element))
every函数主要用于检查是否集合所有元素都满足条件,如果都满足就返回true, 只要存在一个不满足条件的就返回false.
every函数接收一个bool test(E elem