把Flow对象转化成其他类型

76 阅读4分钟

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数据、最终出结果的场景。

总结对比表

操作符返回类型行为/用途找不到时
firstT返回第一个元素/满足条件的元素抛异常
firstOrNullT?同 firstnull
lastT返回最后一个元素抛异常
lastOrNullT?同 lastnull
singleT只有且仅有一个元素时返回,否则抛异常抛异常
singleOrNullT?只有一个元素时返回,否则返回 nullnull
countInt统计元素数量,可传过滤条件0
toListList<T>所有元素转为 List,保留顺序空 List
toSetSet<T>所有元素转为 Set,自动去重空 Set
toCollectionCollection<T>元素放入指定集合(如 ArrayList)空集合
produceInReceiveChannel<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)
}