Kotlin基础知识(八)——惰性集合操作:序列

604 阅读3分钟

前几节中,你看到了很多链式集合函数调用的样子,比如map和filter。这些函数会及早地创建中间集合,也就是说每一步的中间结果都被村粗在一个临时列表。

序列给了你执行这些操作的另一种选择,可以避免创建这些临时中间对象。

people.map(Person::name).filter { it.startsWith("A") }

Kotlin标准库参考文档有说明,filtermap都会返回一个列表。这意味着上面例子中的链式调用会创建两个列表:一个保持filter函数的结果,另一个保存map函数的结果。

为了提高效率,可以把操作变成使用序列,而不是直接使用集合:

// 把初始集合转换成序列
people.asSequence()
         // 序列支持和集合一样的API
        .map(Person::name)
        .filter { it.startsWith("A") }
        //把结果序列转换回序列
        .toList()

Kotlin惰性集合操作的入口就是***Sequence接口。这个接口表示的就是一个可以逐个列举元素的元素序列Sequence只提供了一个方法,iterator***,用来从序列中获取值。

*Sequence*接口的强大之处在于其操作的实现方法。序列中的元素求值是惰性的。因此,可以使用序列更高效地对集合元素执行链式操作,而不需要创建额外的集合来保存过程中产生的中间结果。

先调用扩展函数asSequence把任意集合转换为序列,调用toList来做反向的转换。

  • 为啥要把序列转换回集合?用序列代替集合不是更方便吗?特别是它们还有这么多优点。

答案:有的时候是这样。如果只需要迭代序列中的元素,可以直接使用序列。如果你要用其他的API方法,比如用下标访问元素,则需将序列转换成列表。

一、执行序列操作:中间和末端操作

序列操作分为两类:中间和末端。一次中间操作返回的是另一个序列,这个新序列知道如何变换原始序列中的元素。而一次末端操作返回的是一个结果。

           | ----- 中间操作 ----- |
sequence.map { ... }.filter { ... }.toList()
                                  |-末端操作-|

中间操作始终都是惰性的

  • 缺少末端操作的示例:
>>> listOf(1,2,3,4).asSequence()
            .map { print("map($it)"); it * it }
            .filter { print("filter($it)"); it % 2 == 0 }

执行这段代码并不会在控制台上输入任何内容。这意味着map和filter变换被延期了,它们只有在获取结果的时候才会被应用。

  • 完整示例
>>> listOf(1,2,3,4).asSequence()
            .map { print("map($it)"); it * it }
            .filter { print("filter($it)"); it % 2 == 0 }
            .toList()

// 输出结果
map(1) filter(1) map(2) filter(4) map(3) filter(9) map(4) filter(16)

这个例子中另外一件值得注意的重要事情是计算执行的顺序(其会影响性能)。

二、创建序列

创建序列的方式:

  • asSequence()
  • generateSequence()
>>> val naturalNumbers = generateSequence(0) { it + 1 }
// numbersTo100是一个序列:[0, 1, 2, ... , 100]
>>> val numbersTo100 = naturalNumbers.takeWhile { it <= 100 }
>>> println(numbersTo100.sum())
5050