1. 简介
在日常 Kotlin 开发中,集合操作无处不在。我们经常需要对集合元素或整个集合进行转换处理。幸运的是,Kotlin 标准库提供了丰富的内置方法,让我们能更专注于业务逻辑本身,而不是重复造轮子。
这些 API 设计优雅、链式调用自然,掌握它们不仅能提升编码效率,还能写出更具可读性的函数式风格代码。本文将系统梳理常用集合转换操作,帮助你在实际项目中游刃有余。
2. 过滤元素
过滤是最基础的集合变换之一,即保留满足条件的元素,剔除不符合要求的数据——可以理解为“过筛子”。
基础过滤:filter
使用 filter
方法传入一个 lambda 表达式作为判断条件(谓词),只有返回 true
的元素才会被保留:
val input = listOf(1, 2, 3, 4, 5)
val filtered = input.filter { it <= 3 }
assertEquals(listOf(1, 2, 3), filtered)
你也可以引用外部函数:
fun isSmall(num: Int) = num < 4
val filtered = input.filter(::isSmall)
反向过滤:filterNot
如果想取反条件,可以直接用 filterNot
,比手动加 !
更清晰:
val large = input.filter { !isSmall(it) }
val alsoLarge = input.filterNot(::isSmall)
assertEquals(listOf(4, 5), alsoLarge)
✅ 推荐使用 filterNot
而非 filter { !xxx }
,语义更明确。
按索引过滤:filterIndexed
某些场景下需要结合元素索引做判断,此时可用 filterIndexed
:
val input = listOf(5, 4, 3, 2, 1)
val filtered = input.filterIndexed { index, element -> index < 3 }
assertEquals(listOf(5, 4, 3), filtered)
特殊过滤方法
Kotlin 还提供了一些类型安全的专用过滤方法:
- ✅
filterNotNull()
:从List<T?>
中提取非空值,结果为List<T>
- ✅
filterIsInstance<T>()
:筛选出指定子类型的实例,常用于泛型擦除后的类型还原
示例:
val nullable: List<String?> = listOf("a", null, "b")
val nonnull: List<String> = nullable.filterNotNull() // ["a", "b"]
open class Animal
class Dog : Animal()
val animals: List<Animal> = listOf(Dog(), Animal(), Dog())
val dogs: List<Dog> = animals.filterIsInstance<Dog>()
⚠️ 注意这两个方法会改变集合的实际类型,属于“带转型的过滤”。
3. 映射元素(Map)
除了过滤,我们还经常需要把一种类型的数据映射成另一种形式,这就是 map
的核心用途。
基本映射:map
map
接收一个转换函数,将每个元素逐一转换后生成新集合:
val input = listOf("one", "two", "three")
val reversed = input.map { it.reversed() }
assertEquals(listOf("eno", "owt", "eerht"), reversed)
val lengths = input.map { it.length }
assertEquals(listOf(3, 3, 5), lengths)
无论是同类型变换还是转为完全不同类型(如 String → Int
),API 使用方式完全一致。
带索引映射:mapIndexed
若需访问元素索引,使用 mapIndexed
:
val input = listOf(3, 2, 1)
val result = input.mapIndexed { index, value -> index * value }
assertEquals(listOf(0, 2, 2), result)
处理可能为空的结果:mapNotNull
当映射函数可能返回 null
时,可用 mapNotNull
自动过滤掉空值:
val input = listOf(1, 2, 3, 4, 5)
val smallSquares = input.mapNotNull {
if (it <= 3) it * it else null
}
assertEquals(listOf(1, 4, 9), smallSquares)
✅ 相比先 map
再 filterNotNull()
,mapNotNull
性能更好且更简洁。
3.1 Map 类型的转换
对于 Map<K, V>
,Kotlin 提供了专门的方法来转换键或值:
方法 | 作用 |
---|---|
mapKeys { } |
转换 map 的 key |
mapValues { } |
转换 map 的 value |
示例:
val inputs = mapOf("one" to 1, "two" to 2, "three" to 3)
val squares = inputs.mapValues { it.value * it.value }
assertEquals(mapOf("one" to 1, "two" to 4, "three" to 9), squares)
val uppercases = inputs.mapKeys { it.key.toUpperCase() }
assertEquals(mapOf("ONE" to 1, "TWO" to 2, "THREE" to 3), uppercases)
⚠️ 注意:mapKeys
若产生重复 key,后面的会覆盖前面的,类似于普通 map put 行为。
4. 展平集合(Flattening)
有时 map
的结果是一个嵌套集合(List<List<T>>
),我们需要将其展平为单层结构。
手动展平:flatten
val inputs = listOf("one", "two", "three")
val characters = inputs.map(String::toList)
// 结果:[[o,n,e], [t,w,o], [t,h,r,e,e]]
val flattened = characters.flatten()
assertEquals(listOf('o','n','e','t','w','o','t','h','r','e','e'), flattened)
合并操作:flatMap
flatMap
是 map + flatten
的组合拳,一步到位:
val inputs = listOf("one", "two", "three")
val characters = inputs.flatMap(String::toList)
assertEquals(listOf('o','n','e','t','w','o','t','h','r','e','e'), characters)
✅ 实际开发中 flatMap
更常用,避免中间集合开销。
5. 合并两个集合:Zipping
有时候需要将两个集合按位置配对,形成一一对应的关系,这就是 zip 操作。
基础 zip
val left = listOf("one", "two", "three")
val right = listOf(1, 2, 3)
val zipped = left.zip(right)
// 结果:[(one,1), (two,2), (three,3)]
⚠️ 如果两集合长度不同,结果以较短者为准:
val left = listOf("one", "two")
val right = listOf(1, 2, 3)
val zipped = left.zip(right)
assertEquals(listOf(Pair("one", 1), Pair("two", 2)), zipped)
解压:unzip
与 zip
相反,unzip
可将 List<Pair<A,B>>
拆分为两个列表:
val zipped = listOf(Pair("Alice", 25), Pair("Bob", 30))
val (names, ages) = zipped.unzip()
assertEquals(listOf("Alice", "Bob"), names)
assertEquals(listOf(25, 30), ages)
实战示例:关联数据
常见于 DTO 组装场景:
val posts = fetchPosts()
val authors = posts.map { authorService.getAuthor(it.authorId) }
val result = authors.zip(posts)
.map { (author, post) ->
"《${post.title}》 by ${author.name}"
}
6. 转换为 Map
除了转成同类集合,我们还可以将任意集合转为 Map
结构。
直接转换:toMap
适用于已有 Pair
列表的情况:
val input = listOf(Pair("one", 1), Pair("two", 2))
val map = input.toMap()
assertEquals(mapOf("one" to 1, "two" to 2), map)
关联生成:associateXXX
根据原集合元素动态生成 key 或 value:
方法 | 说明 |
---|---|
associateBy { } |
元素作为 value,lambda 返回 key |
associateWith { } |
元素作为 key,lambda 返回 value |
associate { } |
自定义返回 Pair<K,V> |
示例:
val inputs = listOf("Hi", "there")
// 元素作 key,长度作 value
val map1 = inputs.associateWith { it.length } // {"Hi" -> 2, "there" -> 5}
// 元素作 value,长度作 key
val map2 = inputs.associateBy { it.length } // {2 -> "Hi", 5 -> "there"}
// 自定义 kv 对
val map3 = inputs.associate { e ->
Pair(e.toUpperCase(), e.reversed())
} // {"HI" -> "iH", "THERE" -> "ereht"}
处理重复 Key:groupBy
⚠️ associateBy
遇到重复 key 会覆盖旧值。若要保留所有匹配项,应使用 groupBy
:
val inputs = listOf("one", "two", "three")
val grouped = inputs.groupBy { it.length }
// 结果:{3=["one","two"], 5=["three"]}
✅ 返回 Map<Int, List<String>>
,适合统计、分类等场景。
7. 集合拼接为字符串
有时不需要其他集合结构,而是直接拼成一个字符串输出。
基础拼接:joinToString
val inputs = listOf("Jan", "Feb", "Mar", "Apr", "May")
val simple = inputs.joinToString()
// "Jan, Feb, Mar, Apr, May"
支持自定义分隔符、前后缀:
val detailed = inputs.joinToString(
separator = ",",
prefix = "Months: ",
postfix = "."
)
// "Months: Jan,Feb,Mar,Apr,May."
限制数量与截断
防止日志打印过多内容:
val limited = inputs.joinToString(limit = 3)
// "Jan, Feb, Mar, ..."
可通过 truncated
参数自定义省略符。
边转换边拼接
val upper = inputs.joinToString(transform = String::toUpperCase)
// "JAN, FEB, MAR, APR, MAY"
✅ 比 .map().joinToString()
更高效,只处理实际参与拼接的元素。
写入 Appendable:joinTo
适用于构建大字符串,避免频繁创建临时对象:
val output = StringBuilder()
output.append("My ").append(inputs.size).append(" elements: ")
inputs.joinTo(output)
assertEquals("My 5 elements: Jan, Feb, Mar, Apr, May", output.toString())
8. 归约操作:Reduce & Fold
最后介绍两个强大的聚合方法,用于将集合归约为单一值。
reduce:基于首元素累积
val inputs = listOf("Jan", "Feb", "Mar", "Apr", "May")
val result = inputs.reduce { acc, next -> "$acc, $next" }
// "Jan, Feb, Mar, Apr, May"
⚠️ 缺陷:
- ❌ 不支持空集合(抛异常)
- ❌ 初始值必须是第一个元素,类型受限
fold:指定初始值
更通用的版本,允许自定义初始值和类型:
val totalLength = inputs.fold(0) { acc, str -> acc + str.length }
assertEquals(15, totalLength)
✅ 优势:
- ✅ 支持空集合
- ✅ 初始值任意类型(如
""
,0
,mutableSetOf()
等)
9. 小结
Kotlin 集合 API 设计精巧,覆盖了绝大多数数据处理场景。关键点回顾:
操作类型 | 推荐方法 |
---|---|
过滤 | filter , filterNot , filterNotNull |
映射 | map , mapNotNull , flatMap |
合并 | zip , unzip |
转 Map | associateBy , groupBy , toMap |
拼接 | joinToString , joinTo |
聚合 | fold , reduce |
✅ 建议优先使用 fold
而非 reduce
,避免空集合踩坑。
✅ 链式调用时注意性能,合理使用 xxxTo
系列方法减少中间对象创建。
熟练掌握这些方法,能让 Kotlin 代码更加简洁、安全且富有表现力。