Kotlin 协程 (十) ——— Flow 过渡操作符、限长操作符

748 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

Flow 中有非常多好用的操作符,比如过渡操作符 transform()、map(),再比如限长操作符 take()、drop(),再比如组合/展平操作符 zip()、flatMapConcat()、flatMapMerge() 等等。

如果读者之前接触过响应式编程,那么对这些操作符应该都不会陌生,本文我们将介绍过渡操作符和限长操作符。

一、过渡操作符

过渡操作符用于将流中的每一个元素转换成另一个元素。

1.1. 转换:transform()

转换操作,它会调用 Flow 的 collect() 函数,然后构建一个新的 Flow,这个操作符是很多操作符的基础。

public inline fun <T, R> Flow<T>.transform(
    @BuilderInference crossinline transform: suspend FlowCollector<R>.(value: T) -> Unit
): Flow<R> = flow { // Note: safe flow is used here, because collector is exposed to transform on each operation
    collect { value ->
        // kludge, without it Unit will be returned and TCE won't kick in, KT-28938
        return@collect transform(value)
    }
}

可以看到,transform() 函数调用了原 Flow 的 collect() 函数,然后调用传入的 lambda 表达式转换收集到的值。

如果想要继续发射值,需要重新调用 emit() 函数。

使用示例:

runBlocking {
    flowOf(1, 2, 3).transform {
        emit("transformed $it")
    }.collect {
        println("Collect: $it")
    }
}

运行程序,输出如下:

Collect: transformed 1
Collect: transformed 2
Collect: transformed 3

从 transform() 的源码可以看出,由于 transform() 拦截了 collect() 函数,所以 transform() 函数其实非常灵活,并不是只能用于将收集到的值转换成另一个值。我们完全可以调用多次 emit() 发射多个数据,也可以选择性的发射想要继续发射的数据。

1.2. 映射:map()

映射操作,将一个值映射为另一个值,在响应式编程中非常常见的操作。实际上就是调用了 transform() 操作符,只不过 map() 限制了 transform() 只能将收集到的值转换成另一个值然后继续发射:

源码如下:

public inline fun <T, R> Flow<T>.map(crossinline transform: suspend (value: T) -> R): Flow<R> = transform { value ->
    return@transform emit(transform(value))
}

使用示例:

runBlocking {
    flowOf(1, 2, 3).map {
        "mapped $it"
    }.collect {
        println("Collect: $it")
    }
}

运行程序,输出如下:

Collect: mapped 1
Collect: mapped 2
Collect: mapped 3

二、限长操作符

限长的意思是限制长度,用于选择 Flow 中特定的数据。

比如只取 Flow 的前几个数,或者只取后几个数。Flow 为我们内置了一些筛选条件,开发者也可以自由编写筛选条件。

2.1. 取前几个值:take()

take() 需要传入一个 count,表示取几个数。

runBlocking {
    flowOf(1, 2, 3).take(1).collect {
        println("Collect: $it")
    }
}

运行程序,输出如下:

Collect: 1

2.2. 取满足条件的值:takeWhile()

takeWhile() 需要传入一个 lambda 表达式,只取能够让表达式的返回值为 true 的值。需要注意的是,一旦某个值不满足条件,Flow 会立即结束。

runBlocking {
    flowOf(1, 2, 3).takeWhile {
        it % 2 == 1
    }.collect {
        println("Collect: $it")
    }
    delay(1000)
}

这里我们在 takeWhile() 操作符中判断发射的值是否是奇数。运行程序,输出如下:

Collect: 1

这里之所以没有输出 Collect: 3, 就是因为 takeWhile() 在遇到偶数 2 的时候,中断了整个 Flow。

2.3. 丢弃前几个值:drop()

drop() 与 take() 对应,也需要传入一个 count 值。

2.4. 丢弃满足条件的值 dropWhile()

dropWhile() 与 takeWhile() 对应,传入一个 lambda 表达式,丢弃能够让表达式的返回值为 true 的值。同样地,需要注意的是,一旦某个值不满足条件,后续的值就都不再会被丢弃。

看一个例子:

runBlocking {
    flowOf(1, 2, 1).dropWhile {
        it < 2
    }.collect {
        println("Collect $it")
    }
}

运行程序,输出如下:

Collect 2
Collect 1

这里输出了最后一个值:Collect 1,原因就是 2 已经不满足 it < 2 这个条件了,drop 的工作到 2 就结束了,最后一个 1 不会再被丢弃。

三、小结

本文我们介绍了 Flow 的过渡操作符 transform()、map(),限长操作符 take()、takeWhile()、drop()、dropWhile()。值得注意的是 takeWhile()、dropWhile() 会在条件不满足后立即退出判断,并不是每个值都会被判断。