6、集合框架:List、Set、Map的创建与使用、集合的遍历、集合的过滤与映射、集合的排序
集合框架,说白了就是 Kotlin 里装数据的容器。你写代码,天天跟数据打交道,怎么存、怎么取、怎么筛、怎么排,全得靠它。我个人习惯把集合比作「工具箱」——List 是有序的抽屉,Set 是去重的收纳盒,Map 是带标签的档案柜。今天咱们就把这三个家伙彻底搞明白。
6.1 List:有序的抽屉
List 是最常用的集合类型。它保持元素的插入顺序,并且允许重复。我在项目中处理用户列表、商品列表,几乎天天跟它打交道。
创建 List
Kotlin 提供了两套 API:不可变的 listOf() 和可变的 mutableListOf()。我个人建议:能用不可变就用不可变,能少出 bug。
// 不可变 List —— 创建后不能增删改
val fruits: List<String> = listOf("苹果", "香蕉", "橘子", "苹果")
println(fruits) // [苹果, 香蕉, 橘子, 苹果]
// 可变 List —— 可以随意操作
val mutableFruits = mutableListOf("苹果", "香蕉")
mutableFruits.add("橘子")
mutableFruits.removeAt(0)
println(mutableFruits) // [香蕉, 橘子]
💡 我的习惯: 函数内部如果只是读取数据,一律用
listOf()。只有明确需要增删改时,才用 mutableListOf()。这样别人看你的代码,一眼就知道意图。
List 的常用操作
val numbers = listOf(3, 1, 4, 1, 5, 9, 2, 6)
// 按索引访问
println(numbers[0]) // 3
println(numbers.first()) // 3
println(numbers.last()) // 6
// 判断存在
println(5 in numbers) // true
println(numbers.contains(7)) // false
// 子列表
println(numbers.subList(0, 3)) // [3, 1, 4]
⚠️ 注意:
subList() 返回的是原列表的视图,不是新列表。修改子列表会影响原列表。我曾经在这上面踩过坑,调试了半天才发现是视图引用的问题。
6.2 Set:去重的收纳盒
Set 的核心特性就一个:元素唯一。你想想看,如果你要统计所有不重复的标签、ID、或者用户名,Set 就是最佳选择。
创建 Set
// 不可变 Set
val uniqueNumbers: Set<Int> = setOf(1, 2, 3, 2, 1, 4)
println(uniqueNumbers) // [1, 2, 3, 4] —— 重复的自动去掉
// 可变 Set
val mutableSet = mutableSetOf("Kotlin", "Java", "Kotlin")
mutableSet.add("Python")
println(mutableSet) // [Kotlin, Java, Python]
集合运算
Set 最强大的地方在于数学集合运算。我在做权限系统时,经常用交集、并集来判断用户角色。
val setA = setOf(1, 2, 3, 4)
val setB = setOf(3, 4, 5, 6)
println(setA intersect setB) // [3, 4] —— 交集
println(setA union setB) // [1, 2, 3, 4, 5, 6] —— 并集
println(setA subtract setB) // [1, 2] —— 差集
🔑 核心要点: Set 的查找效率是 O(1),比 List 的 O(n) 快得多。如果你需要频繁判断「某个元素是否存在」,优先用 Set。
6.3 Map:带标签的档案柜
Map 存储的是键值对。每个键唯一,一个键对应一个值。说白了,它就是一本字典——查一个词(键),得到解释(值)。
创建 Map
// 不可变 Map
val scores: Map<String, Int> = mapOf(
"张三" to 95,
"李四" to 87,
"王五" to 92
)
println(scores) // {张三=95, 李四=87, 王五=92}
// 可变 Map
val mutableScores = mutableMapOf<String, Int>()
mutableScores["张三"] = 95
mutableScores["李四"] = 87
mutableScores["王五"] = 92
println(mutableScores)
Map 的常用操作
val map = mapOf("a" to 1, "b" to 2, "c" to 3)
// 取值
println(map["a"]) // 1
println(map.getOrDefault("d", 0)) // 0 —— 避免空指针
// 判断键是否存在
println("b" in map) // true
// 遍历
map.forEach { (key, value) ->
println("$key -> $value")
}
// 获取所有键或值
println(map.keys) // [a, b, c]
println(map.values) // [1, 2, 3]
💡 避坑指南: 用
map["key"] 取值时,如果键不存在,返回 null。我曾经在项目里忘了处理这个 null,结果线上出了 NPE。建议养成用 getOrDefault() 或 getOrElse() 的习惯。
6.4 集合的遍历
遍历是集合操作的基础。Kotlin 提供了多种方式,我按使用频率排个序。
for 循环
val list = listOf("A", "B", "C")
for (item in list) {
println(item)
}
// 带索引
for ((index, value) in list.withIndex()) {
println("索引 $index 的值是 $value")
}
forEach 高阶函数
list.forEach { item ->
println(item)
}
// 带索引的 forEach
list.forEachIndexed { index, item ->
println("[$index] $item")
}
🔑 我的建议: 简单的遍历用
for,可读性更好。需要链式调用(比如先过滤再遍历)时,用 forEach。
6.5 集合的过滤与映射
这是 Kotlin 函数式编程的精髓。你想想看,以前在 Java 里要写一堆循环和临时变量,现在一行代码搞定。
filter —— 过滤
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]
// 过滤出大于 5 的数
val bigNumbers = numbers.filter { it > 5 }
println(bigNumbers) // [6, 7, 8, 9, 10]
// 取反过滤
val notEvens = numbers.filterNot { it % 2 == 0 }
println(notEvens) // [1, 3, 5, 7, 9]
map —— 映射(转换)
val names = listOf("alice", "bob", "charlie")
// 转大写
val upperNames = names.map { it.uppercase() }
println(upperNames) // [ALICE, BOB, CHARLIE]
// 计算字符串长度
val lengths = names.map { it.length }
println(lengths) // [5, 3, 7]
// 链式调用:先过滤再映射
val result = numbers
.filter { it % 2 == 0 }
.map { it * it }
println(result) // [4, 16, 36, 64, 100]
💡 经验之谈: 链式调用时,注意顺序。先
filter 再 map 效率更高,因为减少了后续处理的元素数量。我在优化接口性能时,经常靠调整这俩的顺序来提速。
6.6 集合的排序
排序也是家常便饭。Kotlin 的排序 API 非常直观,基本不需要记什么复杂规则。
自然排序
val unsorted = listOf(3, 1, 4, 1, 5, 9)
val sorted = unsorted.sorted()
println(sorted) // [1, 1, 3, 4, 5, 9]
// 降序
val desc = unsorted.sortedDescending()
println(desc) // [9, 5, 4, 3, 1, 1]
按指定字段排序
data class Person(val name: String, val age: Int)
val people = listOf(
Person("张三", 28),
Person("李四", 22),
Person("王五", 35)
)
// 按年龄升序
val byAge = people.sortedBy { it.age }
println(byAge) // [李四(22), 张三(28), 王五(35)]
// 按年龄降序
val byAgeDesc = people.sortedByDescending { it.age }
println(byAgeDesc) // [王五(35), 张三(28), 李四(22)]
// 先按年龄,再按名字
val sortedByMultiple = people.sortedWith(
compareBy<Person> { it.age }.thenBy { it.name }
)
⚠️ 注意:
sorted() 和 sortedBy() 返回的是新列表,原列表不变。如果你用的是可变列表,想原地排序,用 sort() 和 sortBy()。
6.7 综合实战:用户管理系统
咱们把今天学的知识串起来,写一个真实场景的代码。假设我们要处理一批用户数据。
data class User(
val id: Int,
val name: String,
val age: Int,
val isActive: Boolean
)
val users = listOf(
User(1, "Alice", 25, true),
User(2, "Bob", 17, false),
User(3, "Charlie", 30, true),
User(4, "David", 22, true),
User(5, "Eve", 19, false)
)
// 需求:获取所有活跃用户的名字,按年龄降序排列
val activeUserNames = users
.filter { it.isActive } // 过滤出活跃用户
.sortedByDescending { it.age } // 按年龄降序
.map { it.name } // 只取名字
println(activeUserNames) // [Charlie, Alice, David]
// 需求:统计各年龄段人数
val ageGroups = users.groupBy {
when {
it.age < 18 -> "未成年"
it.age in 18..25 -> "青年"
else -> "成年"
}
}
println(ageGroups)
// {青年=[Alice, David], 未成年=[Bob], 成年=[Charlie, Eve]}
🔑 总结一下: List、Set、Map 各有各的用途。List 管顺序,Set 管去重,Map 管映射。过滤、映射、排序这三个操作,是函数式编程的基石。你只要记住:
filter 做减法,map 做变换,sortedBy 做排列。把这套组合拳打熟了,处理数据就是行云流水。