1. 概述

在 Kotlin 开发中,经常会遇到需要将“列表的列表”(List of Lists)扁平化为单层列表的场景。简单来说,就是把嵌套的多个子列表合并成一个包含所有元素的新列表。

本文将通过实际示例,介绍 Kotlin 中实现列表扁平化的三种主流方法,并分析它们适用的不同场景,帮你避免踩坑✅。


2. 问题背景与典型场景

列表扁平化看似简单,但在实际开发中通常会遇到两类常见情况,处理方式略有不同。

✅ 场景一:直接的列表嵌套

这是最直观的情况 —— 外层是一个 List<List<T>>,每个元素本身就是一个列表。例如:

val listOfList = listOf(
    listOf("Kotlin", "Java"), 
    listOf("Python", "Ruby"), 
    listOf("C++", "Rust", "Go")
)

目标是将其展平为:

val expectedFlatList = listOf("Kotlin", "Java", "Python", "Ruby", "C++", "Rust", "Go")

这类需求很常见,比如从多个 API 接口获取到多个字符串列表后需要合并去重。


✅ 场景二:对象属性中包含列表

更典型的业务场景是,我们有一个对象列表,而每个对象内部持有一个集合类型的属性,我们需要提取所有对象的该属性并合并成一个大列表。

例如定义了一个团队数据类:

data class Team(val name: String, val members: List<String>)

然后有多个团队实例:

val teamA = Team("Team A", listOf("Kai", "Eric"))
val teamB = Team("Team B", listOf("Kevin", "Saajan"))
val teamC = Team("Team C", listOf("Milos", "Tom", "Jerry"))
val teamList = listOf(teamA, teamB, teamC)

最终目标是得到所有成员的名字组成的单一列表:

val expectedFlatMembers = listOf("Kai", "Eric", "Kevin", "Saajan", "Milos", "Tom", "Jerry")

⚠️ 注意:这种情况不能直接使用 flatten(),因为外层不是 List<List<T>> 而是 List<Team>

接下来我们分别用三种方式解决这两个问题。


3. 使用 flatten() 函数

Kotlin 标准库提供了 flatten() 扩展函数,适用于任何实现了 Iterable 或数组类型。

由于 ListIterable 的子类型,因此可以直接调用:

val result = listOfList.flatten()
assertEquals(expectedFlatList, result)

✅ 优点:

  • 语法简洁
  • 专为 Iterable<Iterable<T>> 设计,语义清晰

❌ 局限性:

  • 只能处理直接嵌套的列表结构
  • 无法访问对象内部的 list 属性(如 Team.members
  • 在场景二中完全无能为力

🔍 小贴士:如果你看到编译错误 “Unresolved reference: flatten”,请检查是否导入了正确的包或确认类型确实是 List<List<T>>


4. 使用 flatMap() 函数

flatMap() 是 Kotlin 集合操作中最强大的工具之一,它结合了 mapflatten 的能力。

其核心逻辑是:

对每个元素执行映射生成一个新的列表,然后自动把这些列表拼接成一个扁平结果。

📌 场景一中的使用

val flatList = listOfList.flatMap { it }
assertEquals(expectedFlatList, flatList)

这里 it 就是每一个子列表(List<String>),直接返回即可。

📌 场景二中的使用(这才是重点!)

val flatMembers = teamList.flatMap { it.members }
assertEquals(expectedFlatMembers, flatMembers)

✅ 成功解决了 flatten() 无法触及的问题!

💡 原理说明:

  • flatMap 接收一个 lambda 表达式,返回一个 Iterable
  • 系统会对每个 Team 调用 it.members 得到一个 List<String>
  • 最终将所有 members 列表合并为一个总的 List<String>

📌 这种写法在处理 DTO 转换、权限聚合、标签归并等场景非常实用,建议熟练掌握。


5. 使用 fold() 函数

fold() 提供了一种更底层但灵活的方式:从左到右累积计算。

我们可以利用它来手动拼接每一个子列表:

📌 场景一示例

val flatList = listOfList.fold(listOf<String>()) { acc, str -> acc + str }
assertEquals(expectedFlatList, flatList)

📌 场景二示例

val flatMembers = teamList.fold(listOf<String>()) { list, team -> 
    list + team.members 
}
assertEquals(expectedFlatMembers, flatMembers)

⚠️ 注意性能问题

虽然 fold 可以完成任务,但每次 list + xxx 都会产生新的不可变列表对象,在大数据量下会有明显的性能损耗 ❌。

相比之下:

  • flatten()flatMap() 内部做了优化,效率更高
  • 若使用可变列表(MutableList),可改用 fold(mutableListOf()) { ... apply { addAll(...) } } 提升性能

✅ 适用场景:

  • 需要自定义合并逻辑(比如带条件过滤)
  • 需要同时做聚合和转换

否则优先选择 flatMap() 更简洁安全。


6. 总结对比

方法 是否支持场景一 是否支持场景二 推荐程度 说明
flatten() ⭐⭐ 仅用于 List<List<T>> 直接扁平化
flatMap() ⭐⭐⭐⭐⭐ 最通用,推荐作为首选方案
fold() ⭐⭐⭐ 灵活但易写出低效代码,慎用

最佳实践建议

  • 绝大多数情况下使用 flatMap(),一行搞定,语义清晰
  • 遇到复杂逻辑再考虑 fold()
  • flatten() 仅作了解,实际项目中很少用到

所有示例代码均已上传至 GitHub:https://github.com/baeldung/kotlin-tutorials/tree/master/core-kotlin-modules/core-kotlin-collections-4


原始标题:Flatten List of Lists in Kotlin