天天看點

Flutter 小技巧之 Dart 裡的 List 和 Iterable 你真的搞懂了嗎?

今天我們介紹關于

List

Iterable

裡有趣的知識點 ,你可能會覺得這有什麼好介紹,不就是清單嗎?但是其實在 Dart 裡

List

Iterable

也是很有意思設定,比如有時候我們可以對

List

進行

map

操作,如下代碼所示,你覺得運作之後會列印出什麼内容?

var list = ["1", "2", "3", "4", "5"];
var map = list.map((e) {
 var result = int.parse(e) + 10;
 print("######### $result");
 return result;
});           

複制

答案是:什麼都不會輸出,因為通過

List

傳回一個

Iterable

的操作(如

map

\

where

)的都是 Lazy 的,也就是它們隻會在每次“疊代”時才會被調用。

比如調用

toList();

或者

toString();

等方法,就會觸發上面的

map

執行,進而列印出對應的内容,那新問題來了,假如我們把下圖四個方法都執行一遍,會輸出幾次 log ?em····答案是 3 次。

Flutter 小技巧之 Dart 裡的 List 和 Iterable 你真的搞懂了嗎?

其中除了

isEmpty

之外,其他的三個操作都會重新觸發

map

方法的執行,那究竟是為什麼呢?

其實當我們對一個

List

進行

map

等操作時,傳回的是一個

Iterable

的 Lazy 對象,而每當我們需要通路裡面 value 時,

Iterable

都會重新執行一遍操作,因為它不會對上次操作的結果進行緩存記錄。

是不是有點懵?這裡借用 fast_immutable_collections 作者的一個例子來介紹可能更會清晰,如下代碼所示:

  • 我們對同樣的數組都調用了

    where

    去擷取一個

    Iterable

  • 差別在于在

    evenFilterEager

    裡多調用了

    .toList()

    操作
  • 每次

    where

    執行的時候都對各自的 Counter 進行 +1
  • 最後分别調用三次

    length

    ,輸出 Counter 結果
var lazyCounter = 0;
var eagerCounter = 0;
​
var lazyOddFilter = [1, 2, 3, 4, 5, 6, 7].where((i) {
 lazyCounter++;
 return i % 2 == 0;
});
​
var evenFilterEager = [1, 2, 3, 4, 5, 6, 7].where((i) {
 eagerCounter++;
 return i % 2 == 0;
}).toList();
​
print("\n\n---------- Init ----------\n\n");
​
lazyOddFilter.length;
lazyOddFilter.length;
lazyOddFilter.length;
​
evenFilterEager.length;
evenFilterEager.length;
evenFilterEager.length;
​
print("\n\n---------- Lazy vs Eager ----------\n\n");
​
print("Lazy: $lazyCounter");
print("Eager: $eagerCounter");
​
print("\n\n---------- END ----------\n\n");           

複制

如下圖所示,這個例子最終會輸出 Lazy: 21 Eager: 7 這樣的結果:

  • 因為

    lazyCounter

    每次調用 length 都是直接操作

    Iterable

    這個對象 ,是以每次都會重新執行一次

    where

    ,是以 3 * 7 = 21
  • eagerCounter

    對應的是

    toList();

    ,在調用

    toList();

    時就執行了 7 次

    where

    ,之後不管調用幾次 length 都和

    where

    Iterable

    無關
Flutter 小技巧之 Dart 裡的 List 和 Iterable 你真的搞懂了嗎?

到這裡你應該了解了

Iterable

的 Lazy 性質的特殊之處了吧?

那接下來看一個更新的例子,如下代碼所示,我們依然是分了 eager 和 lazy 兩組做對比,隻是這次我們在

where

裡添加了判斷條件,并且做了嵌套調用,那麼你覺得輸出結果會是什麼?

​
List<int> removeOdd_eager(Iterable<int> source) {
 return source.where((i) {
   print("removeOdd_eager");
   return i % 2 == 0;
}).toList();
}
​
List<int> removeLessThan10_eager(Iterable<int> source) {
 return source.where((i) {
   print("removeLessThan10_eager");
   return i >= 10;
}).toList();
}
​
Iterable<int> removeOdd_lazy(Iterable<int> source) {
 return source.where((i) {
   print("removeOdd_lazy");
   return i % 2 == 0;
});
}
​
Iterable<int> removeLessThan10_lazy(Iterable<int> source) {
 return source.where((i) {
   print("removeLessThan10_lazy");
   return i >= 10;
});
}
​
var list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
​
print("\n\n---------- Init ----------\n\n");
​
Iterable<int> eager = removeLessThan10_eager(removeOdd_eager(list));
​
Iterable<int> lazy = removeLessThan10_lazy(removeOdd_lazy(list));
​
print("\n\n---------- Lazy ----------\n\n");
​
print(lazy);
​
print("\n\n---------- Eager ----------\n\n");
​
print(eager);           

複制

如下所示,可以看到 :

  • 雖然我們先

    print(lazy);

    之後才輸出

    print(eager);

    ,但是先輸出的還是

    removeOdd_eager

    ,因為 Eager 相關的調用裡有

    .toList();

    ,它在

    removeOdd_eager(list)

    時就執行了,是以會先完整輸出

    removeOdd_eager

    之後再完整輸出

    removeLessThan10_eager

    ,最後在我們

    print(eager);

    的時候輸出值
  • lazy 因為是

    Iterable

    ,是以隻有被操作時才會輸出,并且輸出規律是:輸出兩次

    removeOdd_lazy

    之後輸出一次

    removeLessThan10_lazy

    ,因為從資料源 1-15 上,每兩次就符合

    i % 2 == 0;

    的條件,是以會執行

    removeLessThan10_lazy

    ,進而變成這樣的規律執行
I/flutter (23298): ---------- Init ----------
I/flutter (23298): 
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeLessThan10_eager
I/flutter (23298): removeLessThan10_eager
I/flutter (23298): removeLessThan10_eager
I/flutter (23298): removeLessThan10_eager
I/flutter (23298): removeLessThan10_eager
I/flutter (23298): removeLessThan10_eager
I/flutter (23298): removeLessThan10_eager
I/flutter (23298): ---------- Lazy ----------
I/flutter (23298): 
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeLessThan10_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeLessThan10_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeLessThan10_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeLessThan10_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeLessThan10_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeLessThan10_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeLessThan10_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): (10, 12, 14)
I/flutter (23298): ---------- Eager ----------
I/flutter (23298): 
I/flutter (23298): [10, 12, 14]           

複制

是不是很覺得,這種時候

Iterable

把事情變得很複雜? 确實在這種複雜嵌套的時候,

Iterable

會把邏輯變得很難維護,而官方也表示:

由于

Iterable

可能被多次疊代,是以不建議在疊代器中使用 side-effects 。

那了解

Iterable

有什麼用?或者說

Iterable

可以用在什麼場景?其實還是不少, 例如:

  • 分頁,可以確定隻有适合使用者螢幕渲染時,才執行對應邏輯去加載資料
  • 資料庫查詢,可以實作使用資料時執行的懶加載效果,并且每次都重新疊代資料請求

舉個例子,如下代碼所示,感受下

naturalsFunc

這裡

Iterable

配合

Stream

為什麼可以正常:

Iterable<int> naturalsFunc() sync* {
 int k = 0;
 // Infinite loop!
 while (true) yield k++;
}
​
var naturalsIter = naturalsFunc();
​
print("\n\n---------- Init ----------\n\n");
print("The infinite list/iterable was created, but not evaluated.");
print("\n\n--------------------\n\n");
print("\n\n---------- takeWhile ----------\n\n");
print("It's possible to work with it,"
   "but it's necessary to add a method to "
   "stop the processing at some point");
var naturalsUpTo10 = naturalsIter.takeWhile((value) => value <= 10);
print("Naturals up to 10: $naturalsUpTo10");
print("\n\n---------- END ----------\n\n");           

複制

Flutter 小技巧之 Dart 裡的 List 和 Iterable 你真的搞懂了嗎?

那到這裡你可能會問:

List

不也是

Iterable

麼,它和

map

where

expand

等操作傳回的

Iterable

又有什麼差別 ?

如果我們看

List

本身,你會看到它是一個 abstract 對象,它作為

Iterable

的子類,其實一般情況下實作對象會是 dart vm 裡的

_GrowableList

,而

_GrowableList

的結構關系如下圖所示:

Flutter 小技巧之 Dart 裡的 List 和 Iterable 你真的搞懂了嗎?

List

和其他

Iterable

的不同在于在于:

  • List

    是具有長度的可索引集合,因為其内部

    ListIterator

    是通過

    _iterable.length;

    _iterable.elementAt

    來進行實作
  • 普通

    Iterable

    ,如

    map

    操作後的

    MappedIterable

    是按順序通路的集合,通過

    MappedIterator

    來順序通路

    iterable

    的元素,也不關心 length
Flutter 小技巧之 Dart 裡的 List 和 Iterable 你真的搞懂了嗎?

最後做個總結:本篇的知識點很單一,内容也很簡單,就是帶大家快速感受下

List

和一般

Iterable

的差別,并且通過例子了解

Iterable

懶加載的特性和應用場景,這樣有利于在開發過程中

Iterable

進行選型和問題定位。

如果你還有什麼疑惑,歡迎留言評論。