Kotlin 拾遗

89 阅读3分钟

Sequence

是冷流,只有使用时才会执行相关的 lambda 表达式。

  1. 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)
}

其反编译结果如下,可得出:每一步操作都会生成一个新集合,并使用集合当作下一步操作的入参

Xnip2023-06-25_20-42-50.png

如果转成 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 的执行过程为:

  1. 各操作符会都会返回一个 Sequence 对象,内部使用 sequence 记录前一个操作符返回的 Sequence 对象。也就是说这些操作符返回的对象构成一个链表
  2. 使用时逆序遍历链表,依次从前一个节点获取元素,然后执行自己的 transformer,直到最初一个集合只返回最初始元素,然后该元素会顺序经历所有的 lambda 表达式。

双冒号

kt 中双冒号即可以通过对象使用,也可通过接口、抽象类、类使用

  1. 最终会被转换成 Function 对象,该对象会重写 invoke() 方法
  2. 如果通过类使用,调用 invoke 时需要传递对象,指定在哪个对象上执行相应的方法。比如上面 flatMap() 方法,它就是通过接口直接使用,所以在使用时需要传递一个接口的子类实例。
  3. 如果通过对象使用,调用 invoke 时不需要传递对象

Sequence 与协程

如下代码编译会报错:在非协程环境中调用了 suspend 函数。但如果将 asSequence() 去掉,就不会报错。

image.png

主要原因还是 Sequence 在处理时的特殊操作:Sequence 会将每一个操作符转换成一个对象,而 lambda 表达式最终在该对象的某个方法中执行 lambda 表达式,这个方法肯定没有协程环境,所以无法执行 suspend 函数。

对于非 Sequence,它所有的代码都会在当前函数中执行,而当前函数又被包裹在 scope 中,所以可以调用 suspend 函数。