Kotlin序列
序列(Sequences)
序列是一种容器,其中的对象不是事先存储的,而是在迭代过程中动态生成的。 这意味着这些对象只有在真正需要使用时才会被处理:也就是说,序列是惰性执行的(lazy evaluation),每一步不会立刻产生中间结果。
序列提供了与 Iterable
相同的函数:我们可以对其进行过滤、映射、排序等操作,但在底层的实现方式上有所不同。对于 Iterable
,每一步操作都对整个集合执行并返回一个新的中间集合;而对于 Sequence
,每个操作都是逐个元素地链式执行,直到最终结果被请求时才真正开始处理。
这带来了两个优势:
-
序列可以是无限的:我们可以通过初始值和一个函数来定义一个序列,例如无限增长的数列。
-
避免中间集合的创建:多个操作(如过滤、转换等)在序列中是链式按需执行的,不需要每一步都生成新的集合。
创建序列的方法
1. 从元素创建
使用 sequenceOf()
函数,从给定的元素生成序列:
val sequenceOfStrings = sequenceOf("one", "two", "three", "four")
val sequenceOfInts = sequenceOf(1, 2, 3, 4)
2. 从可迭代对象(Iterable)创建
可以使用 asSequence()
将任何集合转换为序列:
val listOfStrings = listOf("one", "two", "three", "four")
val listOfInts = listOf(1, 2, 3, 4)val sequenceOfStrings = listOfStrings.asSequence()
val sequenceOfInts = listOfInts.asSequence()
解释:这会将已有的 List
或其他集合转换为 Sequence
,以便惰性处理元素。
3. 从函数创建
使用 generateSequence()
,它接收一个初始值(种子)和一个函数,根据规则生成一个(可能无限的)序列:
val sequenceOfEvenNumbers = generateSequence(1) { it + 1 }.filter { it % 2 == 0 }.take(5)
println(sequenceOfEvenNumbers.toList()) // [2, 4, 6, 8, 10]
解释:这段代码生成了一个从 1 开始不断加 1 的序列,过滤出偶数,并取前 5 个。
4. 从代码块分块生成
使用 sequence {}
代码块,配合 yield()
或 yieldAll()
按需产生元素:
val evenNumbersSequence = sequence {yield(2)yieldAll(listOf(4, 6))yieldAll(generateSequence(8) { it + 2 })
}
println(evenNumbersSequence.take(5).toList()) // [2, 4, 6, 8, 10]
解释:这个序列依次生成了 2,4,6,然后从 8 开始生成无限个偶数。
一、序列操作的两大分类:无状态 vs 有状态
无状态操作(Stateless)
定义:对每个元素独立处理,不依赖之前元素或上下文。
方法 | 用途 | 示例代码 |
---|---|---|
map() | 对每个元素应用变换 | sequenceOf(1, 2, 3).map { it * 2 } → [2, 4, 6] |
filter() | 过滤符合条件的元素 | sequenceOf(1, 2, 3, 4).filter { it % 2 == 0 } → [2, 4] |
take(n) | 取前 n 个元素 | sequenceOf(1, 2, 3, 4).take(2) → [1, 2] |
drop(n) | 跳过前 n 个元素 | sequenceOf(1, 2, 3, 4).drop(2) → [3, 4] |
这些操作通常开销较小,并且可以尽早终止处理。
有状态操作(Stateful)
定义:需要维护中间状态(缓存、计数、排序等),通常必须遍历整个序列。
方法 | 用途 | 示例代码 |
---|---|---|
sorted() | 排序(需收集全部元素) | sequenceOf(3, 1, 2).sorted() → [1, 2, 3] |
distinct() | 去重(需跟踪已出现的值) | sequenceOf(1, 2, 1).distinct() → [1, 2] |
chunked(n) | 将序列划分为多个块 | sequenceOf(1,2,3,4,5).chunked(2) → [[1,2], [3,4], [5]] |
二、序列操作的阶段:中间 vs 终端
中间操作(Intermediate)
特点:惰性执行,不立刻处理元素,只是构造操作链。返回新的 Sequence
。
方法 | 用途 | 示例 |
---|---|---|
map() | 转换元素 | sequence.map { it * 2 } |
filter() | 过滤元素 | sequence.filter { it > 0 } |
take() / drop() | 取/跳前几个 | sequence.take(3) |
sorted() | 排序 | sequence.sorted() |
这些方法只有在终端操作触发时才执行! |
终端操作(Terminal)
特点:触发执行,返回结果(如集合、单个值等),整个序列会被遍历或处理。
方法 | 用途 | 示例 | 返回值类型 |
---|---|---|---|
toList() | 转换为 List | sequence.toList() | List<T> |
sum() | 求和 | sequenceOf(1,2,3).sum() | Int |
count() | 元素个数 | sequence.count() | Int |
first() / last() | 获取首/尾元素 | sequence.first() | T |
forEach() | 遍历每个元素 | sequence.forEach { println(it) } | Unit |
reduce() / fold() | 累加/聚合 | sequence.reduce { acc, e -> acc + e } | T |
三、完整示例:理解延迟执行与触发机制
fun main() {val result = sequenceOf(1, 2, 3, 4, 5).map {println("map $it")it * 2}.filter {println("filter $it")it > 5}.take(2) // 限制只取前两个符合条件的元素.toList() // 触发执行println("Result: $result")
}
序列与集合的处理方式对比
Iterable
:急切(eager)执行
每个操作都在所有元素上执行,并产生中间集合。
val withIterator = (1..10).filter { print("Filter: $it, "); it % 2 == 0 }.map { print("Mapping: $it, "); it * 2 }.take(3)println()
// 输出:
// Filter: 1, Filter: 2, ... Filter: 10,
// Mapping: 2, Mapping: 4, Mapping: 6, Mapping: 8, Mapping: 10
// 结果:[4, 8, 12]
总共执行了:
-
10 次 filter
-
5 次 map
-
3 次 take
总计:18 次操作
Sequence
:惰性(lazy)执行
每个元素在执行链中逐个被处理,没有中间集合。
val withSequence = (1..10).asSequence().filter { print("Filter: $it, "); it % 2 == 0 }.map { print("Mapping: $it, "); it * 2 }.take(3).toList()println()
// 输出:
// Filter: 1, Filter: 2, Mapping: 2, Filter: 3, Filter: 4, Mapping: 4, ...
// 结果:[4, 8, 12]
📊 总共执行了:
-
6 次 filter
-
3 次 map
-
3 次 take
总计:12 次操作
总结
Sequences(序列) 提供了强大且高效的方式来处理数据集合,特别适合于:
-
大型集合或数据流
-
多步链式处理的场景
-
惰性处理需求
-
表达无限序列或生成序列的逻辑
当你需要优化集合操作性能时,考虑使用 Sequence
是一个很好的选择。