Sequence
是冷流,只有使用时才会执行相关的 lambda 表达式。
Sequence 执行时,一个元素会一次性执行完所有的 lambda 表达式,然后第二个元素执行完所有 lambda 表达式,依次类推。而非 Sequence 却是所有元素依次执行完第一个 lambda,然后依次执行第二个 lambda 表达式。
kt 中有 map, flatMap 等集合操作函数,首先看一下不转成 Sequence 时的逻辑,kt 代码为:
// val map = (1 until 10).asSequence().map{
val map = (1 until 10).map {
it + 2
}.flatMap {
listOf(it)
}
其反编译结果如下,可得出:每一步操作都会生成一个新集合,并使用集合当作下一步操作的入参。
如果转成 Sequence,即使用源码中注释掉的部分,反编译后如下:
SequencesKt.flatMapIterable(
SequencesKt.map(CollectionsKt.asSequence(RangesKt.until(1, 10)),MainActivity$test$1.INSTANCE)
, MainActivity$test$2.INSTANCE);
从中可以看出,它只是生成了一个新对象,并没有执行任何 lambda 表达式,也就是说它是冷流。
而且从代码也可以看出,每一个操作符只是将前面的 sequence 额外的包装一层。flatMap 的源码如下,它的第一个参数就是 map 函数生成的对象,最终返回的是一个 FlatteningSequence 对象
@kotlin.jvm.JvmName("flatMapIterable")
public fun <T, R> Sequence<T>.flatMap(transform: (T) -> Iterable<R>): Sequence<R> {
// 参数 this 为 map{} 生成的对象
// transform 为自定义的 lambda 表达式
// 第三个参数暂时忽略
return FlatteningSequence(this, transform, Iterable<R>::iterator)
}
FlatteningSequence 内部最核心的就是返回一个迭代器,它最核心的逻辑如下:
val iterator = sequence.iterator()
// 该方法会在迭代器的 next() hasNext() 方法中调用
private fun ensureItemIterator(): Boolean {
if (itemIterator?.hasNext() == false)
itemIterator = null
while (itemIterator == null) {
if (!iterator.hasNext()) {
return false
} else {
// iterator 为第一行中通过 sequence 获取的迭代器
// 也就是 element 表示 sequence 中的一个元素
val element = iterator.next()
// 这个 iterator 是构造时传入的参数,也就是 flatMap 中的第三个参数
// transformer 为 flatMap 中的第二个参数
val nextItemIterator = iterator(transformer(element))
if (nextItemIterator.hasNext()) {
itemIterator = nextItemIterator
return true
}
}
}
return true
}
从代码中可以看出,在执行 transformer 之前,它会从自己内部的 sequence 中获取下一个元素。如果再看 map 返回的对象的 next() 方法,可以发现它也是先取内部的 sequence 的下一个元素,然后执行 transformer。因此,Sequence 的执行过程为:
- 各操作符会都会返回一个 Sequence 对象,内部使用 sequence 记录前一个操作符返回的 Sequence 对象。也就是说这些
操作符返回的对象构成一个链表 - 使用时
逆序遍历链表,依次从前一个节点获取元素,然后执行自己的 transformer,直到最初一个集合只返回最初始元素,然后该元素会顺序经历所有的 lambda 表达式。
双冒号
kt 中双冒号即可以通过对象使用,也可通过接口、抽象类、类使用
- 最终会被转换成 Function 对象,该对象会重写 invoke() 方法
- 如果通过
类使用,调用 invoke 时需要传递对象,指定在哪个对象上执行相应的方法。比如上面 flatMap() 方法,它就是通过接口直接使用,所以在使用时需要传递一个接口的子类实例。 - 如果通过对象使用,调用 invoke 时不需要传递对象
Sequence 与协程
如下代码编译会报错:在非协程环境中调用了 suspend 函数。但如果将 asSequence() 去掉,就不会报错。
主要原因还是 Sequence 在处理时的特殊操作:Sequence 会将每一个操作符转换成一个对象,而 lambda 表达式最终在该对象的某个方法中执行 lambda 表达式,这个方法肯定没有协程环境,所以无法执行 suspend 函数。
对于非 Sequence,它所有的代码都会在当前函数中执行,而当前函数又被包裹在 scope 中,所以可以调用 suspend 函数。