序列(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
是一个很好的选择。