Flow 的终结性操作符详解
Flow 的终结性操作符指的是那些最终收集并输出结果,而不会返回新的 Flow 的操作符。它们在内部会触发 Flow 的收集过程(collect),并将处理后的结果作为返回值。
1. first / firstOrNull
-
first()
收集 Flow,遇到第一个元素就停止收集并返回该元素。可以传入条件,只返回第一个满足条件的元素。
若没有找到任何元素,或没有符合条件的元素,则抛出NoSuchElementException。val flow1 = flowOf(1, 2, 3, 4, 5) println("First: ${flow1.first()}") // 输出 1 println("First > 2: ${flow1.first { it > 2 }}") // 输出 3 // 没有元素时抛异常 try { flowOf<Int>().first() } catch (e: NoSuchElementException) { println("No element") } -
firstOrNull()
用法同 first,但没找到元素时返回 null,不会抛异常。kotlin 复制编辑 val flow1 = flowOf(1, 2, 3, 4, 5) println("firstOrNull > 5: ${flow1.firstOrNull { it > 5 }}") // 输出 null
2. last / lastOrNull
- last()
收集整个 Flow,只返回最后一个元素。不支持设置过滤条件。若没有元素会抛异常。 - lastOrNull()
行为同 last,但没有元素时返回 null。
3. single / singleOrNull
- single()
收集 Flow,要求只有唯一一个元素,否则抛异常(没有元素或超过一个都不行)。 - singleOrNull()
只有唯一元素时返回该元素,否则返回 null。
4. count
-
count()
统计 Flow 的元素个数。支持传入过滤条件,统计满足条件的元素数量。val flow1 = flowOf(1, 2, 3, 4, 5) println("Count > 3: ${flow1.count { it > 3 }}") // 输出 2
5. toList / toSet / toCollection
- toList()
收集 Flow 的所有元素到一个 List,顺序保持和 Flow 发射一致。 - toSet()
收集 Flow 的所有元素到一个 Set,自动去重。 - toCollection(collection: C)
低层实现,允许指定目标集合(如 ArrayList/LinkedHashSet)。
6. produceIn
-
produceIn(scope)
把 Flow 转换为 Channel。底层会启动一个协程在指定作用域中 collect Flow,并把数据逐条 send 到返回的 Channel。val channel = flow1.produceIn(scope) // 用 for 循环接收 channel 的数据 for (value in channel) { println(value) }
7. 其它说明
- 这些操作符都是挂起函数,必须在协程环境下调用。
- 都会触发 Flow 的实际收集和计算,结果不是 Flow,而是具体数据类型(如 Int、
List<T>、T 或 null)。 - 常用于需要“聚合”Flow数据、最终出结果的场景。
总结对比表
| 操作符 | 返回类型 | 行为/用途 | 找不到时 |
|---|---|---|---|
| first | T | 返回第一个元素/满足条件的元素 | 抛异常 |
| firstOrNull | T? | 同 first | null |
| last | T | 返回最后一个元素 | 抛异常 |
| lastOrNull | T? | 同 last | null |
| single | T | 只有且仅有一个元素时返回,否则抛异常 | 抛异常 |
| singleOrNull | T? | 只有一个元素时返回,否则返回 null | null |
| count | Int | 统计元素数量,可传过滤条件 | 0 |
| toList | List<T> | 所有元素转为 List,保留顺序 | 空 List |
| toSet | Set<T> | 所有元素转为 Set,自动去重 | 空 Set |
| toCollection | Collection<T> | 元素放入指定集合(如 ArrayList) | 空集合 |
| produceIn | ReceiveChannel<T> | Flow 转为 Channel(异步收集) | - |
学后检测
选择题
1. 关于 first() 和 firstOrNull() 的区别,下面说法正确的是?
A. 两者找不到元素时都会抛出异常
B. first() 找不到元素时抛异常,firstOrNull() 返回 null
C. first() 只能返回 Int 类型
D. firstOrNull() 返回 List
答案:B
解析: first() 没找到会抛出异常,firstOrNull() 返回 null。
2. 下列哪个操作符可以用于将 Flow 的所有元素收集到一个 Set 中?
A. toList()
B. toSet()
C. singleOrNull()
D. first()
答案:B
解析: toSet() 能把所有元素收集到 Set,自动去重。
3. 关于 single(),以下说法正确的是?
A. 流中元素个数为1时返回该元素,否则抛异常
B. 流为空时返回 null
C. 流有多个元素时返回第一个
D. 只能用于 Int 类型 Flow
答案:A
解析: single() 只允许有一个元素,否则抛异常。
4. 下列哪个操作符支持设置过滤条件?
A. count()
B. last()
C. toSet()
D. produceIn()
答案:A
解析: count() 可以加条件,比如 flow.count { it > 3 }。
5. produceIn(scope) 的典型用途是什么?
A. 把 Flow 转为 ReceiveChannel,供协程异步消费
B. 统计 Flow 元素个数
C. 把 Flow 转为 Set
D. 只收集第一个元素
答案:A
解析: produceIn 会自动在 Scope 内协程收集 Flow,并转为 Channel。
判断题
6. lastOrNull() 没有元素时会抛异常。
答案:错
解析: lastOrNull() 没有元素时返回 null,不会抛异常。
7. toList() 和 toSet() 都是挂起函数,必须在协程作用域中调用。
答案:对
解析: 都需要 suspend 环境。
8. count() 不支持条件过滤,只能统计全部元素。
答案:错
解析: count 支持条件:flow.count { it > 0 }
9. produceIn 返回的 Channel 可以被多个消费者并发消费。
答案:错
解析: 普通 Channel 通常只能被一个消费者消费,否则数据可能丢失。
10. singleOrNull() 只有在流有唯一元素时返回该元素,否则返回 null。
答案:对
解析: 没有或有多个都返回 null。
简答题
11. 用一段代码演示:如何获取 Flow 中大于3的第一个元素,若没有则返回 null。
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
val flow = flowOf(1, 2, 3)
val result = flow.firstOrNull { it > 3 }
println(result) // 输出 null
}
12. 简述 toCollection() 与 toList()、toSet() 的区别。给出一个使用 toCollection() 的例子。
答案参考:
toList()、toSet()分别把 Flow 元素收集到新建的 List 或 Set。toCollection()允许你传入一个已有集合,Flow 元素将被添加到该集合中,适合自定义收集逻辑。
例子:
val list = mutableListOf<Int>()
flowOf(1,2,3).toCollection(list)
println(list) // 输出 [1, 2, 3]
13. 假如你希望异步地消费 Flow 数据,可以用哪个操作符?如何使用?
答案参考:
- 用
produceIn(scope),可将 Flow 转为 Channel,在 Channel 上用 for 循环异步消费。 - 用法举例:
val channel = flowOf(1, 2, 3).produceIn(scope)
for (item in channel) {
println(item)
}