第8章:集合框架——List、Set、Map的创建与操作
说实话,Kotlin的集合框架是我最喜欢的一部分。为什么?因为它把「可变」和「不可变」分得清清楚楚,这在Java里可没有这么爽。我记得刚转Kotlin那会儿,写Java写习惯了,总想着用new ArrayList(),结果发现Kotlin里直接listOf()就搞定了。嗯,今天我们就来好好聊聊集合框架。
8.1 集合的三大金刚:List、Set、Map
说白了,集合就是用来装数据的容器。Kotlin提供了三种核心集合类型:
- List:有序的集合,可以重复。就像排队买奶茶,每个人都有一个位置。
- Set:无序的集合,不能重复。就像你的身份证号,全世界只有一个。
- Map:键值对的集合,键唯一。就像查字典,一个词对应一个解释。
我个人习惯把这三者记成「排队、去重、查表」,好记又形象。
8.2 创建集合:从零开始
Kotlin提供了非常简洁的创建方式。你想想看,Java里要写new ArrayList<String>(),Kotlin里只需要:
// 创建不可变集合
val list = listOf("苹果", "香蕉", "橘子")
val set = setOf(1, 2, 3, 3, 3) // 结果只有1,2,3
val map = mapOf("key1" to "value1", "key2" to "value2")
// 创建可变集合
val mutableList = mutableListOf("苹果", "香蕉")
val mutableSet = mutableSetOf(1, 2, 3)
val mutableMap = mutableMapOf("key1" to "value1")
我在项目中遇到过一个问题:新手容易把listOf和mutableListOf搞混。记住一个原则——能用不可变就用不可变,只有确定要修改时才用可变。这能避免很多莫名其妙的bug。
listOf。编译器会提醒你,如果后面试图修改它,那就改成mutableListOf。这叫「最小可变性原则」。
8.3 可变 vs 不可变:一个重要的设计哲学
Kotlin把可变和不可变分成了两套接口。比如List是只读的,MutableList才是可写的。为什么要这么设计?
我曾经在一个大型项目中,因为一个集合被多处代码修改,导致数据错乱,查了两天才找到原因。从那以后,我强制团队:所有函数参数都用不可变集合,除非明确需要修改。
| 集合类型 | 不可变 | 可变 |
|---|---|---|
| List | listOf() |
mutableListOf() |
| Set | setOf() |
mutableSetOf() |
| Map | mapOf() |
mutableMapOf() |
MutableList传给一个第三方库,结果那个库偷偷往里面加了数据,导致我的UI刷新异常。后来我改成list.toList()传过去,问题就解决了。记住:传出去的数据,最好给不可变副本。
8.4 集合的常用操作:增删改查
可变集合的操作很直观。我直接上代码:
val list = mutableListOf("A", "B", "C")
// 增
list.add("D")
list.addAll(listOf("E", "F"))
// 删
list.remove("A")
list.removeAt(0)
list.clear() // 全部清空
// 改
list[0] = "Z"
// 查
val first = list.first()
val last = list.last()
val hasB = "B" in list // true or false
你可能会问:为什么listOf创建的集合不能add?因为它的底层实现就没有写方法。Kotlin在编译时就帮你堵住了这个漏洞,而不是等到运行时才报错。这一点比Java强太多了。
8.5 集合的扩展函数:Kotlin的杀手锏
Kotlin给集合加了一大堆扩展函数,用起来特别爽。我挑几个最常用的:
val numbers = listOf(1, 2, 3, 4, 5, 6)
// 过滤
val evens = numbers.filter { it % 2 == 0 } // [2, 4, 6]
// 映射
val doubled = numbers.map { it * 2 } // [2, 4, 6, 8, 10, 12]
// 分组
val grouped = numbers.groupBy { if (it % 2 == 0) "偶数" else "奇数" }
// 结果:{"奇数"=[1,3,5], "偶数"=[2,4,6]}
// 排序
val sorted = numbers.sortedDescending() // [6,5,4,3,2,1]
// 去重
val withDuplicates = listOf(1, 1, 2, 2, 3)
val unique = withDuplicates.distinct() // [1,2,3]
// 取前N个
val firstThree = numbers.take(3) // [1,2,3]
我个人最常用的是filter和map的组合。比如从用户列表中找出所有活跃用户的名字:
data class User(val name: String, val isActive: Boolean)
val users = listOf(
User("张三", true),
User("李四", false),
User("王五", true)
)
val activeNames = users
.filter { it.isActive }
.map { it.name }
// 结果:["张三", "王五"]
这种链式调用,读起来就像在说「从用户里过滤出活跃的,然后取出名字」。代码即文档,说的就是这种感觉。
8.6 集合的知识体系
下面这张图,是我梳理的集合框架核心脉络。你看一眼就能明白整体结构:
8.7 实战中的选择建议
说了这么多,到底什么时候用哪个?我总结了几条经验:
- 数据展示用List:比如列表页、表格数据,有序且可能需要重复。
- 去重场景用Set:比如标签集合、ID集合,保证唯一性。
- 键值查询用Map:比如配置项、缓存数据,通过键快速取值。
- 函数参数用不可变:避免被意外修改,这是我在代码review时反复强调的。
- 链式操作多用扩展函数:一行代码搞定过滤+映射+排序,比写for循环优雅多了。
📌 核心要点回顾:
- Kotlin把集合分为可变和不可变,这是设计上的进步
- 创建集合用
xxxOf()系列函数,简洁明了 - 扩展函数是Kotlin集合的灵魂,
filter、map、groupBy是高频操作 - 优先使用不可变集合,只在需要修改时用可变集合
好了,集合框架的内容就聊到这儿。你只要把listOf、setOf、mapOf这三个创建方式记住,再把filter和map练熟,日常开发基本就够用了。剩下的扩展函数,用到的时候查一下文档,慢慢就都记住了。