在kotlin中,集合类有很多非常多的好用的扩展方法,但其实还有一个和集合类非常相似的接口Sequence,集合类的扩展方法基本上它也有,那它们有什么区别呢?
A sequence that returns values through its iterator. The values are evaluated lazily, and the sequence is potentially infinite.
这是官方对Sequence的介绍,其中有一点很重要,说它是惰性执行,也就是操作符调用完成不是立刻执行得到结果,而是将操作符缓存,等待触发之后再执行,这是Sequence和Iterable最重要的区别之一。
当Iterable调用完操作符之后就立即执行了操作符中的逻辑并返回了执行完成之后的结果,比如说下面这段代码。
fun main() {
val list = listOf(1, 2, 3).map {
val sq = it * it
println(sq)
sq
}
println("map ok")
println("sum = ${list.sum()}")
}
执行的结果是输出如下,这就说明了map内部逻辑是在输出"map ok"之前执行的。
1
4
9
map ok
sum = 14
而Sequence则不一样,Sequence的操作符分为两类,intermediate和terminal,当intermediate操作符调用完成之后不会执行操作符内部的逻辑,而是将其缓存起来,直到调用了terminal操作符,之前的intermediate才会执行。比如说map
、distinct
、groupBy
这些都是intermediate操作符,它们执行完成之后不会返回的是重新包装的Sequence而不是执行之后的结果,而first
、toList
、count
这些是terminal操作符,它们执行完成之后返回的是最终的结果,而不是Sequence。比如说上面Iterable那段代码换成Sequence之后输出结果的顺序就会不同。
fun main() {
val list = sequenceOf(1, 2, 3).map {
val sq = it * it
println(sq)
sq
}
println("map ok")
println("sum = ${list.sum()}")
}
执行的结果是输出如下,这说明了,在调用map
方法之后,其内部的逻辑并没有立即执行,而是在调用sum
方法之后才执行的。
map ok
1
4
9
sum = 14
Iterable在调用操作符后会直接执行逻辑,得到最终结果再执行下一个操作符,下面看看这段代码的输出。
1fun main() {
2 listOf(1, 2, 3).filter { it ->
3 println("filter $it")
4 it > 0
5 }.map {
6 println("map $it")
7 it
8 }.forEach {
9 println("each $it")
10 }
11}
运行后得到如下输出,filter操作符中所有元素处理完成再开始执行map操作符,同样的map操作符中也是所有的元素处理完成再开始forEach操作符。
filter 1
filter 2
filter 3
map 1
map 2
map 3
each 1
each 2
each 3
而Sequence是按顺序让每一个元素执行完所有的操作符,同样一段代码,看看使用Sequence的输出结果是怎么样的。
1fun main() {
2 sequenceOf(1, 2, 3).filter { it ->
3 println("filter $it")
4 it > 0
5 }.map {
6 println("map $it")
7 it
8 }.forEach {
9 println("each $it")
10 }
11}
运行后得到如下输出,可以看出Sequence中一个元素执行完filter、map和forEach操作符中的代码,然后下一个元素再继续执行filter、map和forEach操作符中的代码。
filter 1
map 1
each 1
filter 2
map 2
each 2
filter 3
map 3
each 3
Iterable和Sequence的执行时机和执行顺序有所不同,我们需要根据具体的场景来选择,另外还有操作符使用的顺序对性能的影响也是很大的。