1. 概述

在 Kotlin 中,我们经常使用集合操作来处理数据。虽然 CollectionSequence 的 API 看起来非常相似,但它们在执行时机上有本质区别。

本文将通过代码示例说明以下几点:

  • Collection立即执行(Eager Evaluation)的
  • Sequence延迟执行(Lazy Evaluation)的
  • 在不同场景下应如何选择使用哪个 API

2. 实现原理对比

2.1. 立即执行(Eager Evaluation)

Collection 的操作是立即执行的。每个操作都会对整个集合中的每个元素执行一次,并将结果保存到一个新的集合中:

(1..100)
  .map { it * it } // 创建一个新列表 [1,4,9,16...10000]
  .filter { it % 2 == 0 } // 再创建一个新列表 [4,16,...10000]
  .first { it > 50 } // 找到第一个大于 50 的元素

例如 filter() 方法的实现如下:

public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
    return filterTo(ArrayList<T>(), predicate)
}

map()filter()flatMap()reduce()fold() 等方法,都是立即执行的,每次都会生成一个中间集合。

2.2. 延迟执行(Lazy Evaluation)

Sequence 的操作是延迟执行的。它不会在每一步都创建中间集合,而是按需处理每个元素,逐个传递下去:

(1..100).asSequence()
  .map { it * it }
  .filter { it % 2 == 0 }
  .first { it > 50 }

在这个例子中,map() 会先对每个数字求平方,然后 filter() 会判断是否为偶数,只有通过过滤的元素才会继续传给 first()

⚠️ 举个例子:1, 9, 25, 49 这些数字虽然通过了 map(),但被 filter() 拒绝了,所以不会进入 first(),也不会被处理。

我们只需要遍历前 8 个数字 [1, 2, 3, 4, 5, 6, 7, 8] 就能拿到结果,避免了 92 次多余的 map()filter() 调用。

为了更清晰地看到执行过程,我们给每个步骤加日志:

(1..100).asSequence()
  .map {
      print("\nTaking: $it")
      (it * it).also { print(" -> Square: $it") }
  }
  .filter { (it.also { print(" -> Even-Filtering: $it") } % 2 == 0) }
  .first { it.also { print(" -> Comparing $it to 50") } > 50 }
  .also { println(" -> Found: $it") }

输出如下:

Taking: 1 -> Square: 1 -> Even-Filtering: 1
Taking: 2 -> Square: 4 -> Even-Filtering: 4 -> Comparing 4 to 50
Taking: 3 -> Square: 9 -> Even-Filtering: 9
Taking: 4 -> Square: 16 -> Even-Filtering: 16 -> Comparing 16 to 50
Taking: 5 -> Square: 25 -> Even-Filtering: 25
Taking: 6 -> Square: 36 -> Even-Filtering: 36 -> Comparing 36 to 50
Taking: 7 -> Square: 49 -> Even-Filtering: 49
Taking: 8 -> Square: 64 -> Even-Filtering: 64 -> Comparing 64 to 50 -> Found: 64

可以看到,每个元素在每一步中被逐一处理,直到找到满足条件的值为止。

2.3. Sequence 内部实现

Sequence Internals

Sequence<T> 的每个操作(如 map()filter())都有自己的实现类,如 TransformingSequenceFilteringSequence,它们不会创建新的 ArrayList<T>

filter() 为例:

public fun <T> Sequence<T>.filter(predicate: (T) -> Boolean): Sequence<T> {
    return FilteringSequence(this, true, predicate)
}

再看 map() 的实现:

public fun <T, R> Sequence<T>.map(transform: (T) -> R): Sequence<R> {
    return TransformingSequence(this, transform)
}

TransformingSequenceiterator() 方法如下:

override fun iterator(): Iterator<R> = object : Iterator<R> {
    val iterator = sequence.iterator()
    override fun next(): R {
        return transformer(iterator.next())
    }

    override fun hasNext(): Boolean {
        return iterator.hasNext()
    }
}

可以看到,Sequence 并不会立即处理所有元素,而是在每次调用 next() 时才进行变换,从而实现了延迟执行。

3. 函数式用法对比

3.1. List<T> vs. Sequence<T>

  • ✅ 返回 List<T> 的操作是 Collection立即执行
  • ✅ 返回 Sequence<T> 的操作是 Sequence延迟执行
public fun <T> listOf(vararg elements: T): List<T>
public fun <T> sequenceOf(vararg elements: T): Sequence<T>

3.2. 使用 Collection 操作

Kotlin 标准库为 Iterable<T> 提供了丰富的扩展函数,比如:

listOf(1, 2, 3, 4, 5, 6)
  .map { it * it } // [4, 16, 36]
  .filter { it % 2 == 0 } // [4, 16, 36]

这些操作无需手动编写循环,就能实现数据转换。

常用操作包括:

  • map()
  • filter()
  • flatMap()
  • reduce()
  • fold()

更多操作请参考 Kotlin 官方文档

3.3. 使用 Sequence 操作

Sequence<T> 的操作分为两类:

  • 中间操作(Intermediate operations):不会立即执行,只保存操作逻辑
  • 终端操作(Terminal operations):如 toList()toSet()first(),触发整个操作链的执行

示例:

val evens = sequenceOf(1, 2, 3, 4, 5)
    .filter { it % 2 == 0 }
    .map { it * it }
    .toList() // [4, 16]

evens.onEach {
  print(it)
}

⚠️ 如果没有 toList(),则 onEach() 不会执行,因为 Sequence 必须有终端操作才能触发整个流程。

4. 性能对比

4.1. 大数据量场景

在处理大数据量时,Collection 的链式操作会频繁创建中间集合,带来显著性能开销。

✅ 推荐使用 Sequence 来优化复杂链式操作,尤其是在处理大型数据集时。

比如,Kotlin Lint 会提示你将 Collection 转换为 Sequence

klint

4.2. 操作顺序影响性能

无论使用 Collection 还是 Sequence,操作顺序对性能都有影响。

❌ 以下写法效率较低:

(1..100)
   .onEach { println(it) } // 打印 100 次
   .filter { it % 2 == 0 }  // 再过滤 100 次

✅ 更高效的方式是先过滤,再打印:

(1..100)
   .filter { it % 2 == 0 } // 先过滤
   .onEach { println(it) } // 只打印 50 次

这样可以减少一半的打印操作,提升效率。

5. 总结

特性 Collection Sequence
执行方式 立即执行 延迟执行
中间集合 每次操作都生成新集合 按需处理,无中间集合
性能 小数据量表现良好 大数据量更高效
适用场景 简单操作、小数据集 复杂链式操作、大数据集

✅ 总结:

  • ✅ 对于小数据集简单操作,使用 Collection 更简洁直观
  • ✅ 对于大数据集复杂链式操作,使用 Sequence 更高效

所有示例代码可参考 GitHub


原始标题:Difference Between Collection and Sequence in Kotlin