1. 概述

本文将深入解析 Kotlin 中 map()flatMap()flatten() 三个集合操作函数的差异。这三个函数在日常开发中高频使用,尤其在处理嵌套结构数据时极易混淆。掌握它们的核心逻辑,能避免不少“踩坑”。

我们将通过实际代码示例说明各自的适用场景,并揭示 flatMap() 本质上是 map() + flatten() 的组合这一关键点。

2. map()

map() 是 Kotlin 集合库中的扩展函数,其定义如下:

fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R>

它的作用是对集合中的每个元素应用一个转换函数,将类型 T 转为 R,最终返回一个 List<R>。✅
典型用于一对一映射场景

举个例子,假设我们有订单和订单明细的结构:

data class Order(val lines: List<OrderLine>)
data class OrderLine(val name: String, val price: Int)

现在有一个订单,想提取所有商品名称:

val order = Order(
    listOf(OrderLine("Tomato", 2), OrderLine("Garlic", 3), OrderLine("Chives", 2))
)
val names = order.lines.map { it.name }

assertThat(names).containsExactly("Tomato", "Garlic", "Chives")

这里我们将 List<OrderLine> 映射为 List<String>,每个元素一对一转换。

再比如计算订单总价:

val totalPrice = order.lines.map { it.price }.sum()
assertEquals(7, totalPrice)

map() 的底层逻辑等价于以下命令式写法:

val result = mutableListOf<R>()
for (each in this) {
    result += transform(each)
}

但使用 map() 时你只需关注 转换逻辑,集合创建、遍历、添加等模板代码已被封装。

3. flatMap()

map() 不同,flatMap() 主要用于处理 一对多关系的扁平化。其函数签名如下:

fun <T, R> Iterable<T>.flatMap(transform: (T) -> Iterable<R>): List<R>

关键点在于:

  • 输入:每个 T 元素被转换成一个 Iterable<R>
  • 输出:不是 List<Iterable<R>>,而是将所有子集合“拍平”后得到 List<R>

来看一个常见业务场景:多个订单中提取所有商品名。

val orders = listOf(
    Order(listOf(OrderLine("Garlic", 1), OrderLine("Chives", 2))),
    Order(listOf(OrderLine("Tomato", 3), OrderLine("Garlic", 4))),
    Order(listOf(OrderLine("Potato", 5), OrderLine("Chives", 6))),
)

如果用 map()

orders.map { it.lines } // 结果是 List<List<OrderLine>> ❌

得到的是嵌套列表,无法直接操作。此时应使用 flatMap()

val lines: List<OrderLine> = orders.flatMap { it.lines }
val names = lines.map { it.name }.distinct()
assertThat(names).containsExactlyInAnyOrder("Garlic", "Chives", "Tomato", "Potato")

flatMap { it.lines } 直接将每个订单的明细列表展开,合并为单一列表。

其命令式等价实现:

val result = mutableListOf<OrderLine>()
for (order in orders) {
    val transformedList = order.lines
    for (individual in transformedList) {
        result += individual
    }
}

显然,flatMap() 帮你省去了外层+内层双重循环的样板代码。

4. flatten()

flatten() 函数专用于 将嵌套的 Iterable 展开一层,不涉及任何元素转换。其定义如下:

public fun <T> Iterable<Iterable<T>>.flatten(): List<T>

示例:

val orderLines = listOf(
    listOf(OrderLine("Garlic", 1), OrderLine("Chives", 2)),
    listOf(OrderLine("Tomato", 3), OrderLine("Garlic", 4)),
    listOf(OrderLine("Potato", 5), OrderLine("Chives", 6)),
)
   
val lines: List<OrderLine> = orderLines.flatten()
val expected = listOf(
    OrderLine("Garlic", 1),
    OrderLine("Chives", 2),
    OrderLine("Tomato", 3),
    OrderLine("Garlic", 4),
    OrderLine("Potato", 5),
    OrderLine("Chives", 6),
)
assertThat(lines).hasSize(6).isEqualTo(expected)

⚠️ 注意:

  • flatten() 不接受任何 lambda 参数
  • 它只做扁平化,不做映射
  • 只展开一层嵌套(如 List<List<T>>List<T>

源码实现也很直观:

public fun <T> Iterable<Iterable<T>>.flatten(): List<T> {
    val result = ArrayList<T>()
    for (element in this) {
        result.addAll(element)
    }
    return result
}

5. map()、flatMap() 与 flatten() 的关系

总结三者职责:

函数 作用
map() 转换,不扁平化
flatten() 扁平化,不转换
flatMap() 先转换,再扁平化 ✅

关键结论:
👉 flatMap() 等价于 map() + flatten()

验证一下:

val orders = listOf(
    Order(listOf(OrderLine("Garlic", 1), OrderLine("Chives", 2))),
    Order(listOf(OrderLine("Tomato", 3), OrderLine("Garlic", 4))),
    Order(listOf(OrderLine("Potato", 5), OrderLine("Chives", 6))),
)
 
val expected = listOf(
    OrderLine("Garlic", 1),
    OrderLine("Chives", 2),
    OrderLine("Tomato", 3),
    OrderLine("Garlic", 4),
    OrderLine("Potato", 5),
    OrderLine("Chives", 6),
)
 
val resultMapAndFlatten: List<OrderLine> = orders.map { it.lines }.flatten()
val resultFlatMap: List<OrderLine> = orders.flatMap { it.lines }
 
assertThat(resultFlatMap).isEqualTo(resultMapAndFlatten).hasSize(6).isEqualTo(expected)

测试通过,两者结果完全一致。

因此你可以这样理解:

  • 当你需要“先映射出子集合,再拍平”时,直接上 flatMap()
  • 手动拆成 map().flatten() 虽然语义清晰,但多一次中间集合创建,略低效

6. 总结

  • map():适合一对一转换,如字段提取、数值计算
  • flatMap():解决一对多扁平化,典型用于嵌套集合展开
  • flatten():纯扁平化工具,适用于已存在嵌套结构的场景
  • 🔑 flatMap() = map() + flatten(),优先使用前者更简洁高效

熟练掌握这三者的差异,能让你的 Kotlin 集合操作更加函数式、简洁且不易出错。

所有示例代码均可在 GitHub 获取:https://github.com/Baeldung/kotlin-tutorials/tree/master/core-kotlin-modules/core-kotlin-collections-2


原始标题:The Difference Between map() and flatMap() in Kotlin