Dart語法篇之集合操做符函數與源碼分析(三)

簡述:數組

在上一篇文章中,咱們全面地分析了經常使用集合的使用以及集合部分源碼的分析。那麼這一節講點更實用的內容,絕對能夠提升你的Flutter開發效率的函數,那就是集合中經常使用的操做符函數。此次說的內容的比較簡單就是怎麼用,以及源碼內部是怎麼實現的。bash

1、Iterable<E>

在dart中幾乎全部集合擁有的操做符函數(例如: map、every、where、reduce等)都是由於繼承或者實現了Iterablemarkdown

一、Iterable類關係圖

二、Iterable類方法圖

2、forEach

一、介紹

void forEach(void f(E element))
複製代碼

forEach在dart中用於遍歷和迭代集合,也是dart中操做集合最經常使用的方法之一。接收一個f(E element)函數做爲參數,返回值類型爲空void.閉包

二、使用方式

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');
     } 
  });
}  
複製代碼

三、源碼解析

void forEach(void f(E element)) {
    //能夠看到在forEach內部實際上就是利用for-in迭代,每迭代一次就執行一次f函數,
    //並把當前element回調出去
    for (E element in this) f(element);
  }
複製代碼

3、map

一、介紹

Iterable<T> map<T>(T f(E e))
複製代碼

map函數主要用於集合中元素的映射,也能夠映射轉化成其餘類型的元素。能夠看到map接收一個T f(E e)函數做爲參數,最後返回一個泛型參數爲TIterable。其實是返回了帶有元素的一個新的惰性Iterable, 而後經過迭代的時候,對每一個元素都調用f函數。注意: f函數是一個接收泛型參數爲E的元素,而後返回一個泛型參數爲T的元素,這就是map能夠將原集合中每一個元素映射成其餘類型元素的緣由。app

二、使用方式

main() {
  var languages = <String>['Dart', 'Kotlin', 'Java', 'Javascript', 'Go', 'Python', 'Swift'];
  print(languages.map((language) => 'develop language is ${language}').join('---'));    
}
複製代碼

三、源碼解析

以上面的例子爲例,ide

  • 一、首先,須要明確一點,languages內部本質是一個_GrowableList<T>, 咱們都知道_GrowableList<T>是繼承了ListBase<T>,而後ListBase<E>又mixin with ListMixin<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>
    ...
}
複製代碼
  • 二、而後能夠看到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);
    ... 
}
複製代碼
  • 三、爲何是惰性的呢,能夠看到它並非直接返回轉化後的集合,而是返回一個帶有值的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));
}
複製代碼
  • 四、通常不會單獨使用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;
  }
複製代碼

4、any

一、介紹

bool any(bool test(E element))
複製代碼

any函數主要用於檢查是否存在任意一個知足條件的元素,只要匹配到第一個就返回true, 若是遍歷全部元素都不符合才返回false. any函數接收一個bool test(E element)函數做爲參數,test函數回調一個E類型的element並返回一個bool類型的值。函數

二、使用方式

main() {
    bool isDartExisted = languages.any((language) => language == 'Dart');
}
複製代碼

三、源碼解析

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;
  }
複製代碼

5、every

一、介紹

bool every(bool test(E element)) 
複製代碼

every函數主要用於檢查是否集合全部元素都知足條件,若是都知足就返回true, 只要存在一個不知足條件的就返回false. every函數接收一個bool test(E element)函數做爲參數,test函數回調一個E類型的element並返回一個bool類型的值。ui

二、使用方式

main() {
    bool isDartAll = languages.every((language) => language == 'Dart');
}
複製代碼

三、源碼解析

bool every(bool test(E element)) {
  //利用for-in遍歷集合,只要找到不符合test函數的條件,就返回false.
    for (E element in this) {
      if (!test(element)) return false;
    }
//遍歷完集合後,找到全部元素符合條件就返回true 
    return true;
  }
複製代碼

6、where

一、介紹

Iterable<E> where(bool test(E element))
複製代碼

where函數主要用於過濾符合條件的元素,相似Kotlin中的filter的做用,最後返回符合條件元素的集合。 where函數接收一個bool test(E element)函數做爲參數,最後返回一個泛型參數爲EIterable。相似map同樣,where這裏也是返回一個惰性的Iterable<E>, 而後對它的iterator進行迭代,對每一個元素都執行test方法。this

二、使用方式

main() {
   List<int> numbers = [0, 3, 1, 2, 7, 12, 2, 4];
   print(numbers.where((num) => num > 6));//輸出: (7,12)
   //注意: 這裏是print的內容實際上輸出的是Iterable的toString方法返回的內容。
}
複製代碼

三、源碼解析

  • 一、首先,須要明確一點numbers其實是一個_GrowableList<T>map的分析原理相似,最終仍是調用了ListMixin中的where函數。
//能夠看到這裏是直接返回一個WhereIterable對象,而不是返回過濾後元素集合,因此它返回的Iterable也是惰性的。
Iterable<E> where(bool test(E element)) => WhereIterable<E>(this, test);
複製代碼
  • 二、而後,繼續深刻研究下WhereIterable是如何實現的
class WhereIterable<E> extends Iterable<E> {
  final Iterable<E> _iterable;//傳入的原集合
  final _ElementPredicate<E> _f;//傳入的where函數中閉包參數

  WhereIterable(this._iterable, this._f);

  //注意: 這裏WhereIterable的迭代藉助了iterator,這裏是直接建立一個WhereIterator,並傳入元集合_iterable中的iterator以及過濾操做函數。
  Iterator<E> get iterator => new WhereIterator<E>(_iterable.iterator, _f);

  // Specialization of [Iterable.map] to non-EfficientLengthIterable.
  Iterable<T> map<T>(T f(E element)) => new MappedIterable<E, T>._(this, f);
}
複製代碼
  • 三、而後,繼續深刻研究下WhereIterator是如何實現的
class WhereIterator<E> extends Iterator<E> {
  final Iterator<E> _iterator;//存儲集合中的iterator對象
  final _ElementPredicate<E> _f;//存儲where函數傳入閉包函數

  WhereIterator(this._iterator, this._f);

  //重寫moveNext函數
  bool moveNext() {
  //遍歷原集合的_iterator
    while (_iterator.moveNext()) {
    //注意: 這裏會執行_f函數,若是知足條件就會返回true, 不符合條件的直接略過,迭代下一個元素;
    //那麼外部迭代時候,就能夠經過current得到當前元素,這樣就實現了在原集合基礎上過濾拿到符合條件的元素。
      if (_f(_iterator.current)) {
        return true;
      }
    }
    //迭代完_iterator全部元素後返回false,以此來終止外部迭代。
    return false;
  }
  //重寫current的屬性方法
  E get current => _iterator.current;
}
複製代碼
  • 四、通常在使用的WhereIterator的時候,外部確定還有一層while迭代,可是這個WhereIterator比較特殊,moveNext()的返回值由where中閉包函數參數返回值決定的,符合條件元素moveNext()就返回true,不符合就略過,迭代檢查下一個元素,直至整個集合迭代完畢,moveNext()返回false,以此也就終止了外部的迭代循環。
  • 五、上面分析,WhereIterable是惰性的,那它啥時候觸發呢? 沒錯就是在迭代它的iterator時候纔會觸發,以上面例子爲例
print(numbers.where((num) => num > 6));//輸出: (7,12),最後會調用Iterable的toString方法返回的內容。

//看下Iterable的toString方法實現
String toString() => IterableBase.iterableToShortString(this, '(', ')');//這就是爲啥輸出樣式是 (7,12)
//繼續查看IterableBase.iterableToShortString
  static String iterableToShortString(Iterable iterable,
      [String leftDelimiter = '(', String rightDelimiter = ')']) {
    if (_isToStringVisiting(iterable)) {
      if (leftDelimiter == "(" && rightDelimiter == ")") {
        // Avoid creating a new string in the "common" case.
        return "(...)";
      }
      return "$leftDelimiter...$rightDelimiter";
    }
    List<String> parts = <String>[];
    _toStringVisiting.add(iterable);
    try {
      _iterablePartsToStrings(iterable, parts);//注意:這裏實際上就是經過將iterable轉化成List,內部就是經過迭代iterator,以此會觸發WhereIterator中的_f函數。
    } finally {
      assert(identical(_toStringVisiting.last, iterable));
      _toStringVisiting.removeLast();
    }
    return (StringBuffer(leftDelimiter)
          ..writeAll(parts, ", ")
          ..write(rightDelimiter))
        .toString();
  }
  
  /// Convert elements of [iterable] to strings and store them in [parts]. 這個函數代碼實現比較多,這裏給出部分代碼
void _iterablePartsToStrings(Iterable iterable, List<String> parts) {
  ...
  int length = 0;
  int count = 0;
  Iterator it = iterable.iterator;
  // Initial run of elements, at least headCount, and then continue until
  // passing at most lengthLimit characters.
  //能夠看到這是外部迭代while
  while (length < lengthLimit || count < headCount) {
    if (!it.moveNext()) return;//這裏實際上調用了WhereIterator中的moveNext函數,通過_f函數處理的moveNext()
    String next = "${it.current}";//獲取current.
    parts.add(next);
    length += next.length + overhead;
    count++;
  }
  ...
}
複製代碼

7、firstWhere和singleWhere和lastWhere

一、介紹

E firstWhere(bool test(E element), {E orElse()})
E lastWhere(bool test(E element), {E orElse()})
E singleWhere(bool test(E element), {E orElse()})
複製代碼

首先從源碼聲明結構上來看,firstWhere、lastWhere和singleWhere是同樣,它們都是接收兩個參數,一個是必需參數:test篩選條件閉包函數,另外一個是可選參數:orElse閉包函數。spa

可是它們用法卻不一樣,firstWhere主要是用於篩選順序第一個符合條件的元素,可能存在多個符合條件元素;lastWhere主要是用於篩選順序最後一個符合條件的元素,可能存在多個符合條件元素;singleWhere主要是用於篩選順序惟一一個符合條件的元素,不可能存在多個符合條件元素,存在的話就會拋出異常IterableElementError.tooMany(), 因此使用它的使用須要謹慎注意下

二、使用方式

main() {
   var numbers = <int>[0, 3, 1, 2, 7, 12, 2, 4];
   //注意: 若是沒有找到,執行orElse代碼塊,可返回一個指定的默認值-1
   print(numbers.firstWhere((num) => num == 5, orElse: () => -1)); 
   //注意: 若是沒有找到,執行orElse代碼塊,可返回一個指定的默認值-1
   print(numbers.lastWhere((num) => num == 2, orElse: () => -1)); 
   //注意: 若是沒有找到,執行orElse代碼塊,可返回一個指定的默認值,前提是集合中只有一個符合條件的元素, 不然就會拋出異常
   print(numbers.singleWhere((num) => num == 4, orElse: () => -1)); 
}
複製代碼

三、源碼解析

//firstWhere
  E firstWhere(bool test(E element), {E orElse()}) {
    for (E element in this) {//直接遍歷原集合,只要找到第一個符合條件的元素就直接返回,終止函數
      if (test(element)) return element;
    }
    if (orElse != null) return orElse();//遍歷完集合後,都沒找到符合條件的元素而且外部傳入了orElse就會觸發orElse函數
    //不然找不到元素,直接拋出異常。因此這裏須要注意下,若是不想拋出異常,可能你須要處理下orElse函數。
    throw IterableElementError.noElement();
  }
  
  //lastWhere
  E lastWhere(bool test(E element), {E orElse()}) {
    E result;//定義result來記錄每次符合條件的元素
    bool foundMatching = false;//定義一個標誌位是否找到符合匹配的。
    for (E element in this) {
      if (test(element)) {//每次找到符合條件的元素,都會重置result,因此result記錄了最新的符合條件元素,那麼遍歷到最後,它也就是最後一個符合條件的元素
        result = element;
        foundMatching = true;//找到後重置標記位
      }
    }
    if (foundMatching) return result;//若是標記位爲true直接返回result便可
    if (orElse != null) return orElse();//處理orElse函數
    //一樣,找不到元素,直接拋出異常。可能你須要處理下orElse函數。
    throw IterableElementError.noElement();
  }

  //singleWhere
  E singleWhere(bool test(E element), {E orElse()}) {
    E result;
    bool foundMatching = false;
    for (E element in this) {
      if (test(element)) {
        if (foundMatching) {//主要注意這裏,只要foundMatching爲true,說明已經找到一個符合條件的元素,若是觸發這條邏輯分支,說明不止一個元素符合條件就直接拋出IterableElementError.tooMany()異常
          throw IterableElementError.tooMany();
        }
        result = element;
        foundMatching = true;
      }
    }
    if (foundMatching) return result;
    if (orElse != null) return orElse();
     //一樣,找不到元素,直接拋出異常。可能你須要處理下orElse函數。
    throw IterableElementError.noElement();
  }
複製代碼

8、join

一、介紹

String join([String separator = ""])
複製代碼

join函數主要是用於將集合全部元素值轉化成字符串,中間用指定的separator鏈接符鏈接。 能夠看到join函數比較簡單,接收一個separator分隔符的可選參數,可選參數默認值是空字符串,最後返回一個字符串。

二、使用方式

main() {
   var numbers = <int>[0, 3, 1, 2, 7, 12, 2, 4];
   print(numbers.join('-'));//輸出: 0-3-1-2-7-12-2-4
}
複製代碼

三、源碼解析

//接收separator可選參數,默認值爲""
  String join([String separator = ""]) {
    Iterator<E> iterator = this.iterator;
    if (!iterator.moveNext()) return "";
    //建立StringBuffer
    StringBuffer buffer = StringBuffer();
    //若是分隔符爲空或空字符串
    if (separator == null || separator == "") {
      //do-while遍歷iterator,而後直接拼接元素
      do {
        buffer.write("${iterator.current}");
      } while (iterator.moveNext());
    } else {
    //若是分隔符不爲空
      //先加入第一個元素
      buffer.write("${iterator.current}");
      //而後while遍歷iterator
      while (iterator.moveNext()) {
        buffer.write(separator);//先拼接分隔符
        buffer.write("${iterator.current}");//再拼接元素
      }
    }
    return buffer.toString();//最後返回最終的字符串。
  }
複製代碼

9、take

一、介紹

Iterable<E> take(int count)
複製代碼

take函數主要是用於截取原集合前count個元素組成的集合,take函數接收一個count做爲函數參數,最後返回一個泛型參數爲EIterable。相似where同樣,take這裏也是返回一個惰性的Iterable<E>, 而後對它的iterator進行迭代。

takeWhile函數主要用於

二、使用方式

main() {
   List<int> numbers = [0, 3, 1, 2, 7, 12, 2, 4];
   print(numbers.take(5));//輸出(0, 3, 1, 2, 7)
}
複製代碼

三、源碼解析

  • 一、首先, 須要明確一點numbers.take調用了ListMixin中的take函數,能夠看到並無直接返回集合前count個元素,而是返回一個TakeIterable<E>惰性Iterable
Iterable<E> take(int count) {
    return TakeIterable<E>(this, count);
  }
複製代碼
  • 二、而後,繼續深刻研究TakeIterable
class TakeIterable<E> extends Iterable<E> {
  final Iterable<E> _iterable;//存儲原集合
  final int _takeCount;//take count

  factory TakeIterable(Iterable<E> iterable, int takeCount) {
    ArgumentError.checkNotNull(takeCount, "takeCount");
    RangeError.checkNotNegative(takeCount, "takeCount");
    if (iterable is EfficientLengthIterable) {//若是原集合是EfficientLengthIterable,就返回建立EfficientLengthTakeIterable
      return new EfficientLengthTakeIterable<E>(iterable, takeCount);
    }
    //不然就返回TakeIterable
    return new TakeIterable<E>._(iterable, takeCount);
  }

  TakeIterable._(this._iterable, this._takeCount);

//注意: 這裏是返回了TakeIterator,並傳入原集合的iterator以及_takeCount
  Iterator<E> get iterator {
    return new TakeIterator<E>(_iterable.iterator, _takeCount);
  }
}
複製代碼
  • 三、而後,繼續深刻研究TakeIterator.
class TakeIterator<E> extends Iterator<E> {
  final Iterator<E> _iterator;//存儲原集合中的iterator
  int _remaining;//存儲須要截取的前幾個元素的數量

  TakeIterator(this._iterator, this._remaining) {
    assert(_remaining >= 0);
  }

  bool moveNext() {
    _remaining--;//經過_remaining做爲遊標控制迭代次數
    if (_remaining >= 0) {//若是_remaining大於等於0就會繼續執行moveNext方法
      return _iterator.moveNext();
    }
    _remaining = -1;
    return false;//若是_remaining小於0就返回false,終止外部循環
  }

  E get current {
    if (_remaining < 0) return null;
    return _iterator.current;
  }
}
複製代碼
  • 四、因此上述例子中最終仍是調用IterabletoString方法,方法中會進行iterator的迭代,最終會觸發惰性TakeIterable中的TakeIteratormoveNext方法。

10、takeWhile

一、介紹

Iterable<E> takeWhile(bool test(E value))
複製代碼

takeWhile函數主要用於依次選擇知足條件的元素,直到遇到第一個不知足的元素,並中止選擇。takeWhile函數接收一個test條件函數做爲函數參數,而後返回一個惰性的Iterable<E>

二、使用方式

main() {
  List<int> numbers = [3, 1, 2, 7, 12, 2, 4];
  print(numbers.takeWhile((number) => number > 2).toList());//輸出: [3] 遇到1的時候就不知足大於2條件就終止篩選。
}
複製代碼

三、源碼解析

  • 一、首先,由於numbersList<int>因此仍是調用ListMixin中的takeWhile函數
Iterable<E> takeWhile(bool test(E element)) {
    return TakeWhileIterable<E>(this, test);//能夠看到它僅僅返回的是TakeWhileIterable,而不是篩選後符合條件的集合,因此它是惰性。
  }
複製代碼
  • 二、而後,繼續看下TakeWhileIterable<E>的實現
class TakeWhileIterable<E> extends Iterable<E> {
  final Iterable<E> _iterable;
  final _ElementPredicate<E> _f;

  TakeWhileIterable(this._iterable, this._f);

  Iterator<E> get iterator {
    //重寫iterator,建立一個TakeWhileIterator對象並返回。
    return new TakeWhileIterator<E>(_iterable.iterator, _f);
  }
}

//TakeWhileIterator
class TakeWhileIterator<E> extends Iterator<E> {
  final Iterator<E> _iterator;
  final _ElementPredicate<E> _f;
  bool _isFinished = false;

  TakeWhileIterator(this._iterator, this._f);

  bool moveNext() {
    if (_isFinished) return false;
    //原集合_iterator遍歷結束或者原集合中的當前元素current不知足_f條件,就返回false以此終止外部的迭代。
    //進一步說明了只有moveNext調用,纔會觸發_f的執行,此時惰性的Iterable才得以執行。
    if (!_iterator.moveNext() || !_f(_iterator.current)) {
      _isFinished = true;//迭代結束重置_isFinished爲true
      return false;
    }
    return true;
  }

  E get current {
    if (_isFinished) return null;//若是迭代結束,還取current就直接返回null了
    return _iterator.current;
  }
}
複製代碼

10、skip

一、介紹

Iterable<E> skip(int count)
複製代碼

skip函數主要是用於跳過原集合前count個元素後,剩下元素組成的集合,skip函數接收一個count做爲函數參數,最後返回一個泛型參數爲EIterable。相似where同樣,skip這裏也是返回一個惰性的Iterable<E>, 而後對它的iterator進行迭代。

二、使用方式

main() {
  List<int> numbers = [3, 1, 2, 7, 12, 2, 4];
  print(numbers.skip(2).toList());//輸出: [2, 7, 12, 2, 4] 跳過前兩個元素3,1 直接從第3個元素開始 
}
複製代碼

三、源碼解析

  • 一、首先,由於numbersList<int>因此仍是調用ListMixin中的skip函數
Iterable<E> skip(int count) => SubListIterable<E>(this, count, null);//返回的是一個SubListIterable惰性Iterable,傳入原集合和須要跳過的count大小
複製代碼
  • 二、而後,繼續看下SubListIterable<E>的實現,這裏只看下elementAt函數實現
class SubListIterable<E> extends ListIterable<E> {
  final Iterable<E> _iterable; // Has efficient length and elementAt.
  final int _start;//這是傳入的須要skip的count
  final int _endOrLength;//這裏傳入爲null
  ...
  int get _endIndex {
    int length = _iterable.length;//獲取原集合長度
    if (_endOrLength == null || _endOrLength > length) return length;//_endIndex爲原集合長度
    return _endOrLength;
  }

  int get _startIndex {//主要看下_startIndex的實現
    int length = _iterable.length;//獲取原集合長度
    if (_start > length) return length;//若是skip的count超過集合自身長度,_startIndex爲原集合長度
    return _start;//不然返回skip的count
  }

  E elementAt(int index) {
    int realIndex = _startIndex + index;//至關於把原集合中每一個元素原來index,總體向後推了_startIndex,最後獲取真實映射的realIndex
    if (index < 0 || realIndex >= _endIndex) {//若是realIndex越界就會拋出異常
      throw new RangeError.index(index, this, "index");
    }
    return _iterable.elementAt(realIndex);//不然就取對應realIndex在原集合中的元素。
  }
  ...
}
複製代碼

11、skipWhile

一、介紹

Iterable<E> skipWhile(bool test(E element))
複製代碼

skipWhile函數主要用於依次跳過知足條件的元素,直到遇到第一個不知足的元素,並中止篩選。skipWhile函數接收一個test條件函數做爲函數參數,而後返回一個惰性的Iterable<E>

二、使用方式

main() {
  List<int> numbers = [3, 1, 2, 7, 12, 2, 4];
  print(numbers.skipWhile((number) => number < 4).toList());//輸出: [7, 12, 2, 4]
  //由於三、一、2都是知足小於4的條件,因此直接skip跳過,直到遇到7不符合條件中止篩選,剩下的就是[7, 12, 2, 4]
}
複製代碼

三、源碼解析

  • 一、首先,由於numbersList<int>因此仍是調用ListMixin中的skipWhile函數
Iterable<E> skipWhile(bool test(E element)) {
    return SkipWhileIterable<E>(this, test);//能夠看到它僅僅返回的是SkipWhileIterable,而不是篩選後符合條件的集合,因此它是惰性的。
  }
複製代碼
  • 二、而後,繼續看下SkipWhileIterable<E>的實現
class SkipWhileIterable<E> extends Iterable<E> {
  final Iterable<E> _iterable;
  final _ElementPredicate<E> _f;

  SkipWhileIterable(this._iterable, this._f);
 //重寫iterator,建立一個SkipWhileIterator對象並返回。
  Iterator<E> get iterator {
    return new SkipWhileIterator<E>(_iterable.iterator, _f);
  }
}

//SkipWhileIterator
class SkipWhileIterator<E> extends Iterator<E> {
  final Iterator<E> _iterator;//存儲原集合的iterator
  final _ElementPredicate<E> _f;//存儲skipWhile中篩選閉包函數
  bool _hasSkipped = false;//判斷是否已經跳過元素的標識,默認爲false

  SkipWhileIterator(this._iterator, this._f);

//重寫moveNext函數
  bool moveNext() {
    if (!_hasSkipped) {//若是是最開始第一次沒有跳過任何元素
      _hasSkipped = true;//而後重置標識爲true,表示已經進行了第一次跳過元素的操做
      while (_iterator.moveNext()) {//迭代原集合中的iterator
        if (!_f(_iterator.current)) return true;//只要找到符合條件的元素,就略過迭代下一個元素,不符合條件就直接返回true終止當前moveNext函數,而此時外部迭代循環正式從當前元素開始迭代,
      }
    }
    return _iterator.moveNext();//那麼遇到第一個不符合條件元素以後全部元素就會經過_iterator.moveNext()正常返回
  }

  E get current => _iterator.current;
}
複製代碼

12、follwedBy

一、介紹

Iterable<E> followedBy(Iterable<E> other)
複製代碼

followedBy函數主要用於在原集合後面追加拼接另外一個Iterable<E>集合,followedBy函數接收一個Iterable<E>參數,最後又返回一個Iterable<E>類型的值。

二、使用方式

main() {
  var languages = <String>['Kotlin', 'Java', 'Dart', 'Go', 'Python'];
  print(languages.followedBy(['Swift', 'Rust', 'Ruby', 'C++', 'C#']).toList());//輸出: [Kotlin, Java, Dart, Go, Python, Swift, Rust, Ruby, C++, C#]
}
複製代碼

三、源碼解析

  • 一、首先,仍是調用ListMixin中的followedBy函數
Iterable<E> followedBy(Iterable<E> other) =>
      FollowedByIterable<E>.firstEfficient(this, other);//這裏實際上仍是返回一個惰性的FollowedByIterable對象,這裏使用命名構造器firstEfficient建立對象
複製代碼
  • 二、而後,繼續看下FollowedByIterable中的firstEfficient實現
factory FollowedByIterable.firstEfficient(
      EfficientLengthIterable<E> first, Iterable<E> second) {
    if (second is EfficientLengthIterable<E>) {//List確定是一個EfficientLengthIterable,因此會建立一個EfficientLengthFollowedByIterable,傳入的參數first是當前集合,second是須要在後面拼接的集合
      return new EfficientLengthFollowedByIterable<E>(first, second);
    }
    return new FollowedByIterable<E>(first, second);
  }
複製代碼
  • 三、而後,繼續看下EfficientLengthFollowedByIterable的實現,這裏只具體看下elementAt函數的實現
class EfficientLengthFollowedByIterable<E> extends FollowedByIterable<E> implements EfficientLengthIterable<E> {
  EfficientLengthFollowedByIterable(
      EfficientLengthIterable<E> first, EfficientLengthIterable<E> second)
      : super(first, second);
 ... 
  E elementAt(int index) {//elementAt在迭代過程會調用
    int firstLength = _first.length;//取原集合的長度
    if (index < firstLength) return _first.elementAt(index);//若是index小於原集合長度就從原集合中獲取元素
    return _second.elementAt(index - firstLength);//不然就經過index - firstLength 計算新的下標從拼接的集合中獲取元素。
  }
  ...
}
複製代碼

十3、expand

一、介紹

Iterable<T> expand<T>(Iterable<T> f(E element)) 
複製代碼

expand函數主要用於將集合中每一個元素擴展爲零個或多個元素或者將多個元素組成二維數組展開成平鋪一個一維數組。 expand函數接收一個Iterable<T> f(E element)函數做爲函數參數。這個閉包函數比較特別,特別之處在於f函數返回的是一個Iterable<T>,那麼就意味着能夠將原集合中每一個元素擴展成多個相同元素。注意expand函數最終仍是返回一個惰性的Iterable<T>

二、使用方式

main() {
   var pair = [
     [1, 2],
     [3, 4]
   ];
   print('flatten list: ${pair.expand((pair) => pair).toList()}');//輸出: flatten list: [1, 2, 3, 4]
   var inputs = [1, 2, 3];
   print('duplicated list: ${inputs.expand((number) => [number, number, number]).toList()}');//輸出: duplicated list: [1, 1, 1, 2, 2, 2, 3, 3, 3]
} 
複製代碼

三、源碼解析

  • 一、首先仍是調用ListMixin中的expand函數。
Iterable<T> expand<T>(Iterable<T> f(E element)) =>
      ExpandIterable<E, T>(this, f);//能夠看到這裏並無直接返回擴展的集合,而是建立一個惰性的ExpandIterable對象返回,
複製代碼
  • 二、而後繼續深刻ExpandIterable
typedef Iterable<T> _ExpandFunction<S, T>(S sourceElement);

class ExpandIterable<S, T> extends Iterable<T> {
  final Iterable<S> _iterable;
  final _ExpandFunction<S, T> _f;

  ExpandIterable(this._iterable, this._f);

  Iterator<T> get iterator => new ExpandIterator<S, T>(_iterable.iterator, _f);//注意: 這裏iterator是一個ExpandIterator對象,傳入的是原集合的iterator和expand函數中閉包函數參數_f
}

//ExpandIterator的實現
class ExpandIterator<S, T> implements Iterator<T> {
  final Iterator<S> _iterator;
  final _ExpandFunction<S, T> _f;
  //建立一個空的Iterator對象_currentExpansion
  Iterator<T> _currentExpansion = const EmptyIterator();
  T _current;

  ExpandIterator(this._iterator, this._f);

  T get current => _current;//重寫current

//重寫moveNext函數,只要當迭代的時候,moveNext執行纔會觸發閉包函數_f執行。
  bool moveNext() {
   //若是_currentExpansion返回false終止外部迭代循環
    if (_currentExpansion == null) return false;
    //開始_currentExpansion是一個空的Iterator對象,因此moveNext()爲false
    while (!_currentExpansion.moveNext()) {
      _current = null;
      //迭代原集合中的_iterator
      if (_iterator.moveNext()) {
        //若是_f拋出異常,先重置_currentExpansion爲null, 遇到 if (_currentExpansion == null) return false;就會終止外部迭代
        _currentExpansion = null;
        _currentExpansion = _f(_iterator.current).iterator;//執行_f函數
      } else {
        return false;
      }
    }
    _current = _currentExpansion.current;
    return true;
  }
}
複製代碼

十4、reduce

一、介紹

E reduce(E combine(E previousValue, E element))
T fold<T>(T initialValue, T combine(T previousValue, E element))
複製代碼

reduce函數主要用於集合中元素依次概括(combine),每次概括後的結果會和下一個元素進行概括,它能夠用來累加或累乘,具體取決於combine函數中操做,combine函數中會回調上一次概括後的值和當前元素值,reduce提供的是獲取累積迭代結果的便利條件. fold和reduce幾乎相同,惟一區別是fold能夠指定初始值。 可是須要注意的是,combine函數返回值的類型必須和集合泛型類型一致。

二、使用方式

main() {
  List<int> numbers = [3, 1, 2, 7, 12, 2, 4];
  print(numbers.reduce((prev, curr) => prev + curr)); //累加
  print(numbers.fold(2, (prev, curr) => (prev as int) + curr)); //累加
  print(numbers.reduce((prev, curr) => prev + curr) / numbers.length); //求平均數
  print(numbers.fold(2, (prev, curr) => (prev as int) + curr) / numbers.length); //求平均數
  print(numbers.reduce((prev, curr) => prev * curr)); //累乘
  print(numbers.fold(2, (prev, curr) => (prev as int) * curr)); //累乘

  var strList = <String>['a', 'b', 'c'];
  print(strList.reduce((prev, curr) => '$prev*$curr')); //拼接字符串
  print(strList.fold('e', (prev, curr) => '$prev*$curr')); //拼接字符串
}
複製代碼

三、源碼解析

E reduce(E combine(E previousValue, E element)) {
    int length = this.length;
    if (length == 0) throw IterableElementError.noElement();
    E value = this[0];//初始值默認取第一個
    for (int i = 1; i < length; i++) {//從第二個開始遍歷
      value = combine(value, this[i]);//combine回調value值和當前元素值,而後把combine的結果概括到value上,依次處理。
      if (length != this.length) {
        throw ConcurrentModificationError(this);//注意: 在操做過程當中不容許刪除和添加元素不然就會出現ConcurrentModificationError
      }
    }
    return value;
  }

  T fold<T>(T initialValue, T combine(T previousValue, E element)) {
    var value = initialValue;//和reduce惟一區別在於這裏value初始值是外部指定的
    int length = this.length;
    for (int i = 0; i < length; i++) {
      value = combine(value, this[i]);
      if (length != this.length) {
        throw ConcurrentModificationError(this);
      }
    }
    return value;
  }
複製代碼

十5、elementAt

一、介紹

E elementAt(int index)
複製代碼

elementAt函數用於獲取對應index下標的元素,傳入一個index參數,返回對應泛型類型E的元素。

二、使用方式

main() {
  print(numbers.elementAt(3));//elementAt通常不會直接使用,更可能是使用[],運算符重載的方式間接使用。 
}
複製代碼

三、源碼解析

E elementAt(int index) {
    ArgumentError.checkNotNull(index, "index");
    RangeError.checkNotNegative(index, "index");
    int elementIndex = 0;
    //for-in遍歷原集合,找到對應elementIndex元素並返回
    for (E element in this) {
      if (index == elementIndex) return element;
      elementIndex++;
    }
    //找不到拋出RangeError
    throw RangeError.index(index, this, "index", null, elementIndex);
  }
複製代碼

總結

到這裏,有關dart中集合操做符函數相關內容就結束了,關於集合操做符函數使用在Flutter中開發很是有幫助,特別在處理集合數據中,可讓你的代碼實現更優雅,不要再是一上來就for循環直接開幹,雖然也能實現,可是若是能適當使用操做符函數,將會使代碼更加簡潔。歡迎繼續關注,下一篇Dart中的函數的使用...

相關文章
相關標籤/搜索