1. 简介

Kotlin Flows 已成为现代异步编程的重要组成部分。它提供了一种简洁、非阻塞的方式来处理异步数据流。在使用 Flow 时,我们经常会用到两个终端操作符:single() 和 *first()*。

虽然这两个方法在功能上看起来有些相似,但它们的行为和适用场景有明显区别。理解这些差异对于编写高效、健壮的代码非常重要。

本文将深入解析 Kotlin Flows 中 single()first() 的区别,并通过示例代码帮助你更好地区分使用。

2. Kotlin Flows 简要回顾

Kotlin Flow 是一种用于异步处理多个值的序列。它允许我们在不阻塞线程的情况下处理数据流,是响应式编程中非常强大的工具。

以下是 Flow 的一些关键特性:

  • ✅ 异步处理:Flow 支持异步地表示和处理一系列值。
  • ✅ 非阻塞:不会阻塞线程,提升程序响应能力。
  • ✅ 支持取消:Flow 支持协程取消,避免资源浪费。
  • ✅ 错误处理:内置错误传播机制,便于统一处理异常。
  • ✅ 与协程集成:Flow 基于 Kotlin 协程构建,天然支持协程环境。

理解这些特性有助于我们更好地掌握 Flow 的终端操作符行为。

3. 使用 single() 方法

single() 方法用于从 Flow 中获取唯一一个元素。如果 Flow 为空,会抛出 NoSuchElementException;如果 Flow 中有多个元素,则抛出 IllegalArgumentException

✅ 也就是说,single() 要求 Flow 中只能有一个元素,否则就抛异常。

示例代码

@Test
fun testSingleValue() = runBlocking {
    val flow = flowOf(42)
    val result = flow.single()
    assertEquals(42, result)
}

上面这个测试用例验证了当 Flow 只包含一个值时,single() 能正确返回该值。

但如果 Flow 中包含多个值,会抛出异常:

@Test
fun testExceptionForMultipleValues() = runBlocking {
    val flow = flowOf(42, 43, 44)
    val exception = assertFailsWith<IllegalArgumentException> {
        flow.single()
    }
    assertEquals("Flow has more than one element", exception.message)
}

同样地,如果 Flow 为空,也会抛异常:

@Test
fun testEmptyFlow() = runBlocking {
    val emptyFlow = flowOf<Int>()
    val exception = assertFailsWith<NoSuchElementException> {
        emptyFlow.single()
    }
    assertEquals("Flow is empty", exception.message)
}

3.1 使用 single() 的场景

建议在以下场景中使用 *single()*:

  • ✅ 你明确知道 Flow 只会 emit 一个值。
  • ✅ 如果有多个值或者为空,应该触发异常(比如数据异常或逻辑错误)。

4. 使用 first() 方法

single() 不同,first() 方法只取 Flow 中的第一个元素。无论 Flow 中有多少个元素,它都只取第一个,然后 Flow 就会被取消。

✅ 也就是说,first() 不关心 Flow 是否有多个值,只要第一个一拿到,任务就完成。

示例代码

@Test
fun testFirstValue() = runBlocking {
    val flow = flowOf(1, 2, 3)
    val result = flow.first()
    assertEquals(1, result)
}

这个测试验证了 first() 能正确返回 Flow 的第一个元素。

但如果 Flow 为空,first() 也会抛出异常:

@Test
fun testFirstValueFromEmptyFlow() = runBlocking {
    val emptyFlow = emptyFlow<Int>()
    val exception = assertFailsWith<NoSuchElementException> {
        emptyFlow.first()
    }
    assertEquals("Expected at least one element", exception.message)
}

⚠️ 注意:first() 不会验证 Flow 是否只有一个元素,只关心第一个值是否存在。

4.1 使用 first() 的场景

建议在以下场景中使用 *first()*:

  • ✅ 你只需要 Flow 的第一个值,不管后面还有多少个。
  • ✅ 不希望 Flow 一直运行,尽早拿到结果并结束。

5. 总结

方法 行为描述 空 Flow 处理 多个值处理
single() 必须只有一个值,否则抛异常 NoSuchElementException IllegalArgumentException
first() 返回第一个值,其余忽略 NoSuchElementException ✅ 正常返回第一个值

总结建议:

  • 如果你确定 Flow 只会有一个值,并且多个值代表错误,使用 *single()*。
  • 如果你只需要 Flow 的第一个值,不管后面还有多少个,使用 *first()*。

这两个方法虽然功能接近,但适用场景完全不同,使用时要根据业务逻辑谨慎选择,避免踩坑。


原始标题:Difference Between single() and first() in Kotlin Flows