Kotlin 集合操作
集合函数式操作
前置知识
- Kotlin 作用域函数:建议先完成前一篇的学习
学习目标
- 掌握「过滤操作」的核心机制、典型用法与常见陷阱
- 掌握「映射操作」的核心机制、典型用法与常见陷阱
- 掌握「查找操作」的核心机制、典型用法与常见陷阱
- 掌握「排序操作」的核心机制、典型用法与常见陷阱
- 掌握「概述」的核心机制、典型用法与常见陷阱
过滤操作
基本写法:filter 过滤元素
<collection>.filter { <predicate> }
// 过滤满足条件的元素
val evens = numbers.filter { it % 2 == 0 };
基本写法:filterNot 反向过滤
<collection>.filterNot { <predicate> }
// 过滤不满足条件的元素
val odds = numbers.filterNot { it % 2 == 0 };
基本写法:filterNotNull 过滤 null
<collection>.filterNotNull()
// 过滤 null 值
val list: List<String?> = listOf("a", null, "b");
val nonNull = list.filterNotNull();
基本写法:filterIndexed 带索引过滤
<collection>.filterIndexed { <index>, <item> -> <predicate> }
// 带索引过滤
val filtered = numbers.filterIndexed { index, _ -> index % 2 == 0 };
基本写法:filterIsInstance 过滤类型
<collection>.filterIsInstance<<Type>>()
// 过滤指定类型
val mixed: List<Any> = listOf(1, "a", 2, "b");
val strings = mixed.filterIsInstance<String>();
基本写法:take 获取前 n 个
<collection>.take(<n>)
// 获取前 n 个元素
val first3 = numbers.take(3);
基本写法:takeLast 获取后 n 个
<collection>.takeLast(<n>)
// 获取后 n 个元素
val last3 = numbers.takeLast(3);
基本写法:drop 丢弃前 n 个
<collection>.drop(<n>)
// 丢弃前 n 个元素
val remaining = numbers.drop(2);
基本写法:dropLast 丢弃后 n 个
<collection>.dropLast(<n>)
// 丢弃后 n 个元素
val remaining = numbers.dropLast(2);
基本写法:takeWhile 条件获取
<collection>.takeWhile { <predicate> }
// 满足条件时获取,遇到不满足时停止
val result = numbers.takeWhile { it < 4 };
基本写法:dropWhile 条件丢弃
<collection>.dropWhile { <predicate> }
// 满足条件时丢弃,遇到不满足时停止
val result = numbers.dropWhile { it < 4 };
基本写法:distinct 去重
<collection>.distinct()
// 去重
val unique = listOf(1, 2, 2, 3, 3).distinct();
基本写法:distinctBy 按条件去重
<collection>.distinctBy { <selector> }
// 按条件去重
val people = listOf(Person("Alice", 25), Person("Bob", 25));
val uniqueAges = people.distinctBy { it.age };
映射操作
基本写法:map 映射元素
<collection>.map { <transform> }
// 映射元素
val doubled = numbers.map { it * 2 };
基本写法:mapIndexed 带索引映射
<collection>.mapIndexed { <index>, <item> -> <transform> }
// 带索引映射
val indexed = numbers.mapIndexed { index, value -> "$index: $value" };
基本写法:mapNotNull 映射并过滤 null
<collection>.mapNotNull { <transform> }
// 映射并过滤 null
val lengths = listOf("a", null, "bb").mapNotNull { it?.length };
基本写法:flatMap 扁平映射
<collection>.flatMap { <transform> }
// 扁平映射
val nested = listOf(listOf(1, 2), listOf(3, 4));
val flat = nested.flatMap { it };
基本写法:flatten 扁平化
<collection>.flatten()
// 扁平化嵌套集合
val flat = nested.flatten();
基本写法:groupBy 分组
<collection>.groupBy { <keySelector> }
// 按条件分组
val grouped = numbers.groupBy { if (it % 2 == 0) "even" else "odd" };
基本写法:groupBy 带值转换
<collection>.groupBy({ <keySelector> }, { <valueTransform> })
// 分组并转换值
val grouped = people.groupBy({ it.age }, { it.name });
基本写法:chunked 分块
<collection>.chunked(<size>)
// 分块处理
val chunks = numbers.chunked(2);
基本写法:windowed 滑动窗口
<collection>.windowed(<size>, <step>, <partialWindows>)
// 滑动窗口
val windows = numbers.windowed(3, 1, false);
基本写法:zip 合并集合
<list1>.zip(<list2>)
// 合并两个集合
val names = listOf("Alice", "Bob");
val ages = listOf(25, 30);
val pairs = names.zip(ages);
基本写法:zip 合并并转换
<list1>.zip(<list2>) { <a>, <b> -> <transform> }
// 合并并转换
val combined = names.zip(ages) { name, age -> "$name: $age" };
基本写法:unzip 拆分
<list>.unzip()
// 拆分 Pair 列表
val pairs = listOf("a" to 1, "b" to 2);
val (keys, values) = pairs.unzip();
基本写法:partition 分区
<collection>.partition { <predicate> }
// 按条件分区为两个列表
val (evens, odds) = numbers.partition { it % 2 == 0 };
查找操作
基本写法:find 查找第一个匹配
<collection>.find { <predicate> }
// 查找第一个匹配元素
val first = numbers.find { it > 3 };
基本写法:findLast 查找最后一个匹配
<collection>.findLast { <predicate> }
// 查找最后一个匹配元素
val last = numbers.findLast { it > 3 };
基本写法:firstOrNull 获取第一个元素
<collection>.firstOrNull()
// 获取第一个元素,空列表返回 null
val first = numbers.firstOrNull();
基本写法:firstOrNull 条件查找
<collection>.firstOrNull { <predicate> }
// 查找第一个满足条件的元素
val first = numbers.firstOrNull { it > 3 };
基本写法:lastOrNull 获取最后一个元素
<collection>.lastOrNull()
// 获取最后一个元素,空列表返回 null
val last = numbers.lastOrNull();
基本写法:lastOrNull 条件查找
<collection>.lastOrNull { <predicate> }
// 查找最后一个满足条件的元素
val last = numbers.lastOrNull { it > 3 };
基本写法:indexOf 查找索引
<list>.indexOf(<element>)
// 查找元素索引
val index = numbers.indexOf(3);
基本写法:binarySearch 二分查找
<list>.binarySearch(<element>)
// 二分查找(列表需有序)
val index = sortedList.binarySearch(5);
基本写法:elementAtOrNull 安全获取
<list>.elementAtOrNull(<index>)
// 安全获取指定索引元素
val element = numbers.elementAtOrNull(10);
基本写法:elementAtOrElse 条件获取
<list>.elementAtOrElse(<index>) { <default> }
// 获取指定索引元素,越界返回默认值
val element = numbers.elementAtOrElse(10) { -1 };
排序操作
基本写法:sorted 升序排序
<collection>.sorted()
// 升序排序
val sorted = numbers.sorted();
基本写法:sortedDescending 降序排序
<collection>.sortedDescending()
// 降序排序
val sorted = numbers.sortedDescending();
基本写法:sortedBy 按条件升序
<collection>.sortedBy { <selector> }
// 按条件升序排序
val sorted = people.sortedBy { it.age };
基本写法:sortedByDescending 按条件降序
<collection>.sortedByDescending { <selector> }
// 按条件降序排序
val sorted = people.sortedByDescending { it.age };
基本写法:sortedWith 自定义排序
<collection>.sortedWith(<comparator>)
// 自定义比较器排序
val sorted = people.sortedWith(compareBy({ it.age }, { it.name }));
基本写法:reversed 反转
<collection>.reversed()
// 反转集合
val reversed = numbers.reversed();
基本写法:shuffled 随机打乱
<collection>.shuffled()
// 随机打乱集合
val shuffled = numbers.shuffled();
概述
Kotlin 的集合操作是其最强大的特性之一。通过丰富的扩展函数,你可以用简洁的函数式风格对集合进行过滤、映射、排序、分组、聚合等操作,而不需要写繁琐的 for 循环。这些操作大多以 lambda 表达式作为参数,让代码既简洁又易读。
如果你有 Python 或 JavaScript 的背景,Kotlin 的集合操作会让你感到熟悉。但 Kotlin 的类型系统让这些操作更加安全。
基础概念
- List:有序集合,可以重复,分为 MutableList(可变)和 List(不可变)
- Set:无序集合,不可以重复,分为 MutableSet 和 Set
- Map:键值对集合,分为 MutableMap 和 Map
- Iterable:所有集合的父接口,支持迭代
- Sequence:懒序列,类似 Java 的 Stream,中间操作不会立即执行
- 高阶函数:接受函数作为参数的函数,如 map、filter、forEach 等
快速上手
fun main() {
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// 过滤:只保留偶数
val evens = numbers.filter { it % 2 == 0 }
println("偶数: $evens") // [2, 4, 6, 8, 10]
// 映射:每个元素乘以2
val doubled = numbers.map { it * 2 }
println("翻倍: $doubled") // [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
// 链式调用:先过滤再映射
val result = numbers.filter { it % 2 == 0 }.map { it * it }
println("偶数的平方: $result") // [4, 16, 36, 64, 100]
// 排序
val sorted = numbers.sortedDescending()
println("降序: $sorted") // [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
// 求和
val sum = numbers.sum()
println("总和: $sum") // 55
}
详细用法
分组和聚合
fun groupDemo() {
data class Student(val name: String, val grade: String, val score: Int)
val students = listOf(
Student("Alice", "A", 90),
Student("Bob", "B", 80),
Student("Charlie", "A", 95),
Student("David", "B", 75),
Student("Eve", "A", 88)
)
// groupBy:按属性分组
val byGrade = students.groupBy { it.grade }
println("A组: ${byGrade["A"]?.map { it.name }}") // [Alice, Charlie, Eve]
println("B组: ${byGrade["B"]?.map { it.name }}") // [Bob, David]
// groupingBy + aggregate:更灵活的分组聚合
val avgScoreByGrade = students.groupingBy { it.grade }.average()
println("A组平均分: ${avgScoreByGrade["A"]}")
// count:计数
val countByGrade = students.groupingBy { it.grade }.eachCount()
println(countByGrade) // {A=3, B=2}
// 聚合函数
val scores = students.map { it.score }
println("最高分: ${scores.max()}")
println("最低分: ${scores.min()}")
println("平均分: ${scores.average()}")
println("总分: ${scores.sum()}")
}
Map 操作
fun mapOpsDemo() {
val scores = mapOf("Alice" to 90, "Bob" to 80, "Charlie" to 95)
// 遍历
scores.forEach { (name, score) ->
println("$name: $score")
}
// mapKeys / mapValues:转换键或值
val upperKeys = scores.mapKeys { it.key.uppercase() }
println(upperKeys) // {ALICE=90, BOB=80, CHARLIE=95}
val graded = scores.mapValues { if (it.value >= 90) "A" else "B" }
println(graded) // {Alice=A, Bob=B, Charlie=A}
// filterKeys / filterValues:过滤
val highScores = scores.filterValues { it >= 90 }
println(highScores) // {Alice=90, Charlie=95}
// getOrDefault / getOrElse
println(scores.getOrDefault("David", 0)) // 0
println(scores.getOrElse("David") { 0 }) // 0
// toList:转为键值对列表
val pairs = scores.toList()
println(pairs) // [(Alice, 90), (Bob, 80), (Charlie, 95)]
}
Sequence 懒序列
fun sequenceDemo() {
val numbers = (1..100).toList()
// 普通集合操作:每一步都创建新集合
val listResult = numbers
.filter { it % 2 == 0 } // 创建中间集合
.map { it * it } // 又创建中间集合
.take(5) // 再创建中间集合
// Sequence:懒执行,不创建中间集合
val seqResult = numbers.asSequence()
.filter { it % 2 == 0 } // 不执行
.map { it * it } // 不执行
.take(5) // 不执行
.toList() // 到这里才执行,且每个元素走完整个管道
println(listResult) // [4, 16, 36, 64, 100]
println(seqResult) // [4, 16, 36, 64, 100]
// Sequence 在数据量大时性能更好
// 因为不需要创建中间集合
}
常见场景
数据转换管道
data class RawUser(val name: String, val age: String, val email: String?)
data class ValidUser(val name: String, val age: Int, val email: String)
fun processUsers(rawUsers: List<RawUser>): List<ValidUser> {
return rawUsers
.filter { it.email != null } // 过滤掉没有邮箱的
.map { // 转换数据
ValidUser(
name = it.name.trim(),
age = it.age.toIntOrNull() ?: 0,
email = it.email!!
)
}
.filter { it.age >= 18 } // 过滤掉未成年
.sortedBy { it.name } // 按名字排序
.distinctBy { it.email } // 按邮箱去重
}
频率统计
fun frequencyDemo() {
val text = "hello world kotlin programming"
// 统计每个字符出现的次数
val charFreq = text.groupingBy { it }.eachCount()
println(charFreq)
// 统计每个单词出现的次数
val wordFreq = text.split(" ").groupingBy { it }.eachCount()
println(wordFreq)
// 找出出现次数最多的元素
val words = listOf("a", "b", "a", "c", "a", "b")
val mostCommon = words.groupingBy { it }.eachCount()
.maxByOrNull { it.value }
println("最常见的: $mostCommon") // a=3
}
集合的交并差
fun setOperations() {
val a = setOf(1, 2, 3, 4, 5)
val b = setOf(4, 5, 6, 7, 8)
// 交集
val intersect = a intersect b
println("交集: $intersect") // {4, 5}
// 并集
val union = a union b
println("并集: $union") // {1, 2, 3, 4, 5, 6, 7, 8}
// 差集
val subtract = a subtract b
println("差集: $subtract") // {1, 2, 3}
}
注意事项
- 优先使用不可变集合:
listOf、mapOf、setOf创建不可变集合,减少意外修改的风险 - Sequence 适合大数据量:当集合元素很多且链式操作很长时,用
asSequence()避免创建中间集合 - 避免在 map 中做过滤:用
filter+map代替在map中返回 null,更清晰 - 注意空集合的聚合:对空集合调用
max()、average()等会抛异常,使用maxOrNull()等安全版本 - distinctBy 会保留第一个:当有重复键时,
distinctBy保留第一个遇到的元素
进阶用法
自定义聚合
// fold:带初始值的累积
val numbers = listOf(1, 2, 3, 4, 5)
val sum = numbers.fold(0) { acc, num -> acc + num }
println(sum) // 15
// 用 fold 构建字符串
val result = numbers.fold("Numbers:") { acc, num -> "$acc $num" }
println(result) // Numbers: 1 2 3 4 5
// reduce:不带初始值的累积(集合不能为空)
val product = numbers.reduce { acc, num -> acc * num }
println(product) // 120
窗口和分块
val numbers = (1..10).toList()
// chunked:按大小分块
val chunks = numbers.chunked(3)
println(chunks) // [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]
// windowed:滑动窗口
val windows = numbers.windowed(3, step = 2)
println(windows) // [[1, 2, 3], [3, 4, 5], [5, 6, 7], [7, 8, 9], [9, 10]]
// zip:合并两个集合
val names = listOf("Alice", "Bob", "Charlie")
val ages = listOf(25, 30, 20)
val pairs = names.zip(ages)
println(pairs) // [(Alice, 25), (Bob, 30), (Charlie, 20)]
// unzip:拆分
val (names2, ages2) = pairs.unzip()
关联操作
data class Product(val id: Int, val name: String, val price: Double)
val products = listOf(
Product(1, "手机", 2999.0),
Product(2, "电脑", 5999.0),
Product(3, "耳机", 299.0)
)
// associateBy:按某个属性建立 Map
val byId = products.associateBy { it.id }
println(byId[2]) // Product(id=2, name=电脑, price=5999.0)
// associateWith:用元素本身作为键
val priceMap = products.associateWith { it.price }
println(priceMap) // {Product(1,手机,2999.0)=2999.0, ...}
// associate:自定义键值
val namePriceMap = products.associate { it.name to it.price }
println(namePriceMap) // {手机=2999.0, 电脑=5999.0, 耳机=299.0}
集合创建
基本写法:listOf 创建只读列表
listOf(<elements>)
// 创建只读列表
val numbers = listOf(1, 2, 3, 4, 5);
基本写法:mutableListOf 创建可变列表
mutableListOf(<elements>)
// 创建可变列表
val mutableList = mutableListOf(1, 2, 3);
mutableList.add(4);
基本写法:setOf 创建只读集合
setOf(<elements>)
// 创建只读集合(去重)
val set = setOf(1, 2, 3, 3); // {1, 2, 3}
基本写法:mutableSetOf 创建可变集合
mutableSetOf(<elements>)
// 创建可变集合
val mutableSet = mutableSetOf(1, 2, 3);
mutableSet.add(4);
基本写法:mapOf 创建只读映射
mapOf(<key1> to <value1>, <key2> to <value2>)
// 创建只读映射
val map = mapOf("a" to 1, "b" to 2);
基本写法:mutableMapOf 创建可变映射
mutableMapOf(<key1> to <value1>)
// 创建可变映射
val mutableMap = mutableMapOf("a" to 1);
mutableMap["b"] = 2;
基本写法:emptyList 创建空列表
emptyList<<Type>>()
// 创建空列表
val empty: List<String> = emptyList();
基本写法:arrayListOf 创建 ArrayList
arrayListOf(<elements>)
// 创建 ArrayList
val arrayList = arrayListOf(1, 2, 3);
基本写法:linkedMapOf 创建 LinkedHashMap
linkedMapOf(<key1> to <value1>)
// 创建 LinkedHashMap(保持插入顺序)
val linkedMap = linkedMapOf("a" to 1, "b" to 2);
集合基本操作
基本写法:size 获取大小
<collection>.size
// 获取集合大小
val size = numbers.size;
基本写法:contains 检查包含
<collection>.contains(<element>)
// 检查是否包含元素
numbers.contains(3);
基本写法:in 检查包含
<element> in <collection>
// 使用 in 检查包含
3 in numbers;
基本写法:!in 检查不包含
<element> !in <collection>
// 使用 !in 检查不包含
6 !in numbers;
基本写法:isEmpty 检查空集合
<collection>.isEmpty()
// 检查集合是否为空
numbers.isEmpty();
基本写法:isNotEmpty 检查非空集合
<collection>.isNotEmpty()
// 检查集合是否非空
numbers.isNotEmpty();
基本写法:get 获取元素
<list>[<index>]
// 通过索引获取元素
val first = numbers[0];
基本写法:get 获取 Map 值
<map>[<key>]
// 通过键获取值
val value = map["a"];