Kotlin Sequence (序列)

在處理集合 (Collections) 時,我們最常用的是 List。但在某些情況下(資料量很大、或操作步驟很多),Sequence 提供了更好的效能與彈性。

核心差異:惰性求值 (Lazy Evaluation)

  • List (Iterable)及早求值 (Eager Evaluation) 的。
    • 每執行一個步驟(如 filter),就會馬上計算並產生一個新的中間列表
  • Sequence惰性求值 (Lazy Evaluation) 的。
    • 它不會產生中間列表。它會逐個元素執行完整個操作鏈。

視覺化差異

假設我們要處理:filter (取偶數) -> map (乘以 2) -> first (取第一個)。

List 的做法 (Horizontal Processing):

  1. 先找出所有的偶數 (ArrayList)。
  2. 所有偶數乘以 2 (ArrayList)。
  3. 取出第一個。 (如果 List 有 100 萬筆資料,前兩步會非常浪費記憶體與時間)

Sequence 的做法 (Vertical Processing):

  1. 拿第 1 個元素 -> 是偶數嗎? -> 乘以 2 -> 是我們要的嗎?(Done!)
  2. (後面的 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 的操作分為兩類:

  1. 中間操作 (Intermediate):回傳 Sequence

    • 無狀態 (Stateless)filter, map, take
    • 有狀態 (Stateful)sorted, distinct (需要讀完所有資料才能排序/去重)。
    • 特性:這些操作不會馬上執行,只是把邏輯記下來。
  2. 終端操作 (Terminal):回傳結果 (List, Int, Boolean 等)。

    • toList(), sum(), count(), first(), forEach()
    • 特性:只有呼叫了終端操作,水管裡的水才會開始流動 (觸發計算)。

該怎麼選? (List vs Sequence)

特性List (Iterable)Sequence
資料量大 / 無限
操作步驟少 (1-2 步)多 (鍊式操作長)
記憶體每個步驟都會建立新的 List (高)不會建立中間 List (低)
存取方式可隨機存取 (get(i))只能順序讀取 (迭代)

結論:

  • 一般日常開發,資料量少時,直接用 List 即可 (效能差異無感,且 List 除錯方便)。
  • 如果有非常長的過濾/轉換鍊,或者資料來源是大量/無限的,請務必切換成 Sequence