6、集合框架:List、Set、Map的创建与使用、集合的遍历、集合的过滤与映射、集合的排序

集合框架,说白了就是 Kotlin 里装数据的容器。你写代码,天天跟数据打交道,怎么存、怎么取、怎么筛、怎么排,全得靠它。我个人习惯把集合比作「工具箱」——List 是有序的抽屉,Set 是去重的收纳盒,Map 是带标签的档案柜。今天咱们就把这三个家伙彻底搞明白。

Kotlin 集合框架核心体系 集合框架 List(有序可重复) Set(无序不可重复) Map(键值对) listOf() / mutableListOf() 遍历:for / forEach / forEachIndexed setOf() / mutableSetOf() 去重特性 / 集合运算 mapOf() / mutableMapOf() 键唯一 / 快速查找 公共操作:过滤 · 映射 · 排序 filter / filterNot map / flatMap sortedBy / sortedByDescending

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]
💡 经验之谈: 链式调用时,注意顺序。先 filtermap 效率更高,因为减少了后续处理的元素数量。我在优化接口性能时,经常靠调整这俩的顺序来提速。

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 做排列。把这套组合拳打熟了,处理数据就是行云流水。
公众号:蓝海资料掘金营,微信deep3321