Kotlin Sequence (序列)
在處理集合 (Collections) 時,我們最常用的是 List。但在某些情況下(資料量很大、或操作步驟很多),Sequence 提供了更好的效能與彈性。
核心差異:惰性求值 (Lazy Evaluation)
- List (Iterable) 是 及早求值 (Eager Evaluation) 的。
- 每執行一個步驟(如
filter),就會馬上計算並產生一個新的中間列表。
- 每執行一個步驟(如
- Sequence 是 惰性求值 (Lazy Evaluation) 的。
- 它不會產生中間列表。它會逐個元素執行完整個操作鏈。
視覺化差異
假設我們要處理:filter (取偶數) -> map (乘以 2) -> first (取第一個)。
List 的做法 (Horizontal Processing):
- 先找出所有的偶數 (
ArrayList)。 - 把所有偶數乘以 2 (
ArrayList)。 - 取出第一個。 (如果 List 有 100 萬筆資料,前兩步會非常浪費記憶體與時間)
Sequence 的做法 (Vertical Processing):
- 拿第 1 個元素 -> 是偶數嗎? -> 乘以 2 -> 是我們要的嗎?(Done!)
- (後面的 99 萬筆資料完全不會被讀取到)
val list = listOf(1, 2, 3, 4, 5, 6)
// List: 每個步驟都產生新 List
val eager = list
.filter { println("Filter $it"); it % 2 == 0 }
.map { println("Map $it"); it * 2 }
.first()
println("---")
// Sequence: 只有需要時才執行
val lazy = list.asSequence()
.filter { println("Filter $it"); it % 2 == 0 }
.map { println("Map $it"); it * 2 }
.first()
// 輸出結果比較:
// List 會印出所有的 Filter 1~6 和 Map 2,4,6
// Sequence 只會印到 2 (Filter 1, Filter 2, Map 2) 就停止了,因為找到 first 就收工
建立 Sequence
1. 從集合轉換
最常用的方式。
val seq = list.asSequence()
2. 直接建立
val seq = sequenceOf("A", "B", "C")
3. 本身產生器 (generateSequence)
適合用來產生無限串流或依賴前一個狀態的序列。
// 產生無限的自然數:0, 1, 2, 3 ...
val infinite = generateSequence(0) { it + 1 }
// 取前 5 個
val first5 = infinite.take(5).toList()
// [0, 1, 2, 3, 4]
對於無限序列,請務必使用
take() 限制數量,否則呼叫 toList() 或 count() 等終端操作會導致無窮迴圈 (Out of Memory)。4. 區塊生成 (sequence { yield })
這是最強大的方式,可以使用 yield 關鍵字暫停並產出值(類似 Python 的 Generator 或 Coroutine)。
val fibonacci = sequence {
var a = 0
var b = 1
yield(a) // 產出 0
yield(b) // 產出 1
while (true) {
val temp = a + b
yield(temp)
a = b
b = temp
}
}
println(fibonacci.take(10).toList())
操作分類
Sequence 的操作分為兩類:
中間操作 (Intermediate):回傳
Sequence。- 無狀態 (Stateless):
filter,map,take。 - 有狀態 (Stateful):
sorted,distinct(需要讀完所有資料才能排序/去重)。 - 特性:這些操作不會馬上執行,只是把邏輯記下來。
- 無狀態 (Stateless):
終端操作 (Terminal):回傳結果 (List, Int, Boolean 等)。
toList(),sum(),count(),first(),forEach()。- 特性:只有呼叫了終端操作,水管裡的水才會開始流動 (觸發計算)。
該怎麼選? (List vs Sequence)
| 特性 | List (Iterable) | Sequence |
|---|---|---|
| 資料量 | 小 | 大 / 無限 |
| 操作步驟 | 少 (1-2 步) | 多 (鍊式操作長) |
| 記憶體 | 每個步驟都會建立新的 List (高) | 不會建立中間 List (低) |
| 存取方式 | 可隨機存取 (get(i)) | 只能順序讀取 (迭代) |
結論:
- 一般日常開發,資料量少時,直接用
List即可 (效能差異無感,且 List 除錯方便)。 - 如果有非常長的過濾/轉換鍊,或者資料來源是大量/無限的,請務必切換成
Sequence。