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
或数组类型。
由于 List
是 Iterable
的子类型,因此可以直接调用:
val result = listOfList.flatten()
assertEquals(expectedFlatList, result)
✅ 优点:
- 语法简洁
- 专为
Iterable<Iterable<T>>
设计,语义清晰
❌ 局限性:
- 只能处理直接嵌套的列表结构
- 无法访问对象内部的 list 属性(如
Team.members
) - 在场景二中完全无能为力
🔍 小贴士:如果你看到编译错误 “Unresolved reference: flatten”,请检查是否导入了正确的包或确认类型确实是
List<List<T>>
。
4. 使用 flatMap()
函数
flatMap()
是 Kotlin 集合操作中最强大的工具之一,它结合了 map
和 flatten
的能力。
其核心逻辑是:
对每个元素执行映射生成一个新的列表,然后自动把这些列表拼接成一个扁平结果。
📌 场景一中的使用
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