本文已参与「新人创作礼」活动,一起开启掘金创作之路。
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() 会在条件不满足后立即退出判断,并不是每个值都会被判断。