今天我們介紹關于
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 次。
其中除了
isEmpty
之外,其他的三個操作都會重新觸發
map
方法的執行,那究竟是為什麼呢?
其實當我們對一個
List
進行
map
等操作時,傳回的是一個
Iterable
的 Lazy 對象,而每當我們需要通路裡面 value 時,
Iterable
都會重新執行一遍操作,因為它不會對上次操作的結果進行緩存記錄。
是不是有點懵?這裡借用 fast_immutable_collections 作者的一個例子來介紹可能更會清晰,如下代碼所示:
- 我們對同樣的數組都調用了
去擷取一個where
Iterable
- 差別在于在
裡多調用了evenFilterEager
操作.toList()
- 每次
執行的時候都對各自的 Counter 進行 +1where
- 最後分别調用三次
,輸出 Counter 結果length
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 這樣的結果:
- 因為
每次調用 length 都是直接操作lazyCounter
這個對象 ,是以每次都會重新執行一次Iterable
,是以 3 * 7 = 21where
- 而
對應的是eagerCounter
,在調用toList();
時就執行了 7 次toList();
,之後不管調用幾次 length 都和where
的where
無關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);
,因為 Eager 相關的調用裡有removeOdd_eager
,它在.toList();
時就執行了,是以會先完整輸出removeOdd_eager(list)
之後再完整輸出removeOdd_eager
,最後在我們removeLessThan10_eager
的時候輸出值print(eager);
- lazy 因為是
,是以隻有被操作時才會輸出,并且輸出規律是:輸出兩次Iterable
之後輸出一次removeOdd_lazy
,因為從資料源 1-15 上,每兩次就符合removeLessThan10_lazy
的條件,是以會執行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");
複制
那到這裡你可能會問:
List
不也是
Iterable
麼,它和
map
、
where
、
expand
等操作傳回的
Iterable
又有什麼差別 ?
如果我們看
List
本身,你會看到它是一個 abstract 對象,它作為
Iterable
的子類,其實一般情況下實作對象會是 dart vm 裡的
_GrowableList
,而
_GrowableList
的結構關系如下圖所示:
而
List
和其他
Iterable
的不同在于在于:
-
是具有長度的可索引集合,因為其内部List
是通過ListIterator
和_iterable.length;
來進行實作_iterable.elementAt
- 普通
,如Iterable
操作後的map
是按順序通路的集合,通過MappedIterable
來順序通路MappedIterator
的元素,也不關心 lengthiterable
最後做個總結:本篇的知識點很單一,内容也很簡單,就是帶大家快速感受下
List
和一般
Iterable
的差別,并且通過例子了解
Iterable
懶加載的特性和應用場景,這樣有利于在開發過程中
Iterable
進行選型和問題定位。
如果你還有什麼疑惑,歡迎留言評論。