Dart語法篇之集合操作符函式與原始碼分析(三)

mikyou發表於2019-11-05

簡述:

在上一篇文章中,我們全面地分析了常用集合的使用以及集合部分原始碼的分析。那麼這一節講點更實用的內容,絕對可以提高你的Flutter開發效率的函式,那就是集合中常用的操作符函式。這次說的內容的比較簡單就是怎麼用,以及原始碼內部是怎麼實現的。

一、Iterable<E>

在dart中幾乎所有集合擁有的操作符函式(例如: map、every、where、reduce等)都是因為繼承或者實現了Iterable

1、Iterable類關係圖

Dart語法篇之集合操作符函式與原始碼分析(三)

2、Iterable類方法圖

Dart語法篇之集合操作符函式與原始碼分析(三)

二、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)函式作為引數,最後返回一個泛型引數為TIterable。實際上是返回了帶有元素的一個新的惰性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 with ListMixin<E>.所以languages.map函式呼叫就是呼叫ListMixin<E>中的map函式,實際上還是相當於呼叫了自身的成員函式map.

Dart語法篇之集合操作符函式與原始碼分析(三)

@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 element)函式作為引數,test函式回撥一個E型別的element並返回一個bool型別的值。

2、使用方式

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

3、原始碼解析

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

六、where

1、介紹

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

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

2、使用方式

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

3、原始碼解析

  • 1、首先,需要明確一點numbers實際上是一個_GrowableList<T>map的分析原理類似,最終還是呼叫了ListMixin中的where函式。
//可以看到這裡是直接返回一個WhereIterable物件,而不是返回過濾後元素集合,所以它返回的Iterable也是惰性的。
Iterable<E> where(bool test(E element)) => WhereIterable<E>(this, test);
複製程式碼
  • 2、然後,繼續深入研究下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);
}
複製程式碼
  • 3、然後,繼續深入研究下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;
}
複製程式碼
  • 4、一般在使用的WhereIterator的時候,外部肯定還有一層while迭代,但是這個WhereIterator比較特殊,moveNext()的返回值由where中閉包函式引數返回值決定的,符合條件元素moveNext()就返回true,不符合就略過,迭代檢查下一個元素,直至整個集合迭代完畢,moveNext()返回false,以此也就終止了外部的迭代迴圈。
  • 5、上面分析,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++;
  }
  ...
}
複製程式碼

七、firstWhere和singleWhere和lastWhere

1、介紹

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閉包函式。

但是它們用法卻不同,firstWhere主要是用於篩選順序第一個符合條件的元素,可能存在多個符合條件元素;lastWhere主要是用於篩選順序最後一個符合條件的元素,可能存在多個符合條件元素;singleWhere主要是用於篩選順序唯一一個符合條件的元素,不可能存在多個符合條件元素,存在的話就會丟擲異常IterableElementError.tooMany(), 所以使用它的使用需要謹慎注意下

2、使用方式

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)); 
}
複製程式碼

3、原始碼解析

  //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();
  }
複製程式碼

八、join

1、介紹

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

join函式主要是用於將集合所有元素值轉化成字串,中間用指定的separator連線符連線。 可以看到join函式比較簡單,接收一個separator分隔符的可選引數,可選引數預設值是空字串,最後返回一個字串。

2、使用方式

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

3、原始碼解析

  //接收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();//最後返回最終的字串。
  }
複製程式碼

九、take

1、介紹

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

take函式主要是用於擷取原集合前count個元素組成的集合,take函式接收一個count作為函式引數,最後返回一個泛型引數為EIterable。類似where一樣,take這裡也是返回一個惰性的Iterable<E>, 然後對它的iterator進行迭代。

takeWhile函式主要用於

2、使用方式

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

3、原始碼解析

  • 1、首先, 需要明確一點numbers.take呼叫了ListMixin中的take函式,可以看到並沒有直接返回集合前count個元素,而是返回一個TakeIterable<E>惰性Iterable
  Iterable<E> take(int count) {
    return TakeIterable<E>(this, count);
  }
複製程式碼
  • 2、然後,繼續深入研究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);
  }
}
複製程式碼
  • 3、然後,繼續深入研究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;
  }
}
複製程式碼
  • 4、所以上述例子中最終還是呼叫IterabletoString方法,方法中會進行iterator的迭代,最終會觸發惰性TakeIterable中的TakeIteratormoveNext方法。

十、takeWhile

1、介紹

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

takeWhile函式主要用於依次選擇滿足條件的元素,直到遇到第一個不滿足的元素,並停止選擇。takeWhile函式接收一個test條件函式作為函式引數,然後返回一個惰性的Iterable<E>

2、使用方式

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

3、原始碼解析

  • 1、首先,因為numbersList<int>所以還是呼叫ListMixin中的takeWhile函式
  Iterable<E> takeWhile(bool test(E element)) {
    return TakeWhileIterable<E>(this, test);//可以看到它僅僅返回的是TakeWhileIterable,而不是篩選後符合條件的集合,所以它是惰性。
  }
複製程式碼
  • 2、然後,繼續看下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;
  }
}
複製程式碼

十、skip

1、介紹

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

skip函式主要是用於跳過原集合前count個元素後,剩下元素組成的集合,skip函式接收一個count作為函式引數,最後返回一個泛型引數為EIterable。類似where一樣,skip這裡也是返回一個惰性的Iterable<E>, 然後對它的iterator進行迭代。

2、使用方式

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

3、原始碼解析

  • 1、首先,因為numbersList<int>所以還是呼叫ListMixin中的skip函式
Iterable<E> skip(int count) => SubListIterable<E>(this, count, null);//返回的是一個SubListIterable惰性Iterable,傳入原集合和需要跳過的count大小
複製程式碼
  • 2、然後,繼續看下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在原集合中的元素。
  }
  ...
}
複製程式碼

十一、skipWhile

1、介紹

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

skipWhile函式主要用於依次跳過滿足條件的元素,直到遇到第一個不滿足的元素,並停止篩選。skipWhile函式接收一個test條件函式作為函式引數,然後返回一個惰性的Iterable<E>

2、使用方式

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

3、原始碼解析

  • 1、首先,因為numbersList<int>所以還是呼叫ListMixin中的skipWhile函式
  Iterable<E> skipWhile(bool test(E element)) {
    return SkipWhileIterable<E>(this, test);//可以看到它僅僅返回的是SkipWhileIterable,而不是篩選後符合條件的集合,所以它是惰性的。
  }
複製程式碼
  • 2、然後,繼續看下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;
}
複製程式碼

十二、follwedBy

1、介紹

Iterable<E> followedBy(Iterable<E> other)
複製程式碼

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

2、使用方式

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#]
}
複製程式碼

3、原始碼解析

  • 1、首先,還是呼叫ListMixin中的followedBy函式
  Iterable<E> followedBy(Iterable<E> other) =>
      FollowedByIterable<E>.firstEfficient(this, other);//這裡實際上還是返回一個惰性的FollowedByIterable物件,這裡使用命名構造器firstEfficient建立物件
複製程式碼
  • 2、然後,繼續看下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);
  }
複製程式碼
  • 3、然後,繼續看下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 計算新的下標從拼接的集合中獲取元素。
  }
  ...
}
複製程式碼

十三、expand

1、介紹

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

expand函式主要用於將集合中每個元素擴充套件為零個或多個元素或者將多個元素組成二維陣列展開成平鋪一個一維陣列。 expand函式接收一個Iterable<T> f(E element)函式作為函式引數。這個閉包函式比較特別,特別之處在於f函式返回的是一個Iterable<T>,那麼就意味著可以將原集合中每個元素擴充套件成多個相同元素。注意expand函式最終還是返回一個惰性的Iterable<T>

2、使用方式

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]
} 
複製程式碼

3、原始碼解析

  • 1、首先還是呼叫ListMixin中的expand函式。
  Iterable<T> expand<T>(Iterable<T> f(E element)) =>
      ExpandIterable<E, T>(this, f);//可以看到這裡並沒有直接返回擴充套件的集合,而是建立一個惰性的ExpandIterable物件返回,
複製程式碼
  • 2、然後繼續深入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;
  }
}
複製程式碼

十四、reduce

1、介紹

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函式返回值的型別必須和集合泛型型別一致。

2、使用方式

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')); //拼接字串
}
複製程式碼

3、原始碼解析

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

十五、elementAt

1、介紹

E elementAt(int index)
複製程式碼

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

2、使用方式

main() {
  print(numbers.elementAt(3));//elementAt一般不會直接使用,更多是使用[],運算子過載的方式間接使用。 
}
複製程式碼

3、原始碼解析

  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中的函式的使用...

相關文章