Kotlin 集合
集合是 Kotlin 中存储和操作数据组的核心工具。Kotlin 标准库提供了丰富的集合类型和操作函数,让数据处理变得简洁高效。本章将详细介绍 List、Set、Map 以及各种操作函数。
集合类型概述
Kotlin 提供三种基本集合类型,每种都有只读和可变两种形式:
| 类型 | 描述 | 只读 | 可变 |
|---|---|---|---|
| List | 有序集合,允许重复元素 | List | MutableList |
| Set | 唯一元素集合 | Set | MutableSet |
| Map | 键值对集合 | Map | MutableMap |
集合继承体系
Iterable
└── Collection
├── List ────── MutableList
└── Set ─────── MutableSet
Map ──────────────────────── MutableMap
关键理解:
- 只读集合不等于不可变:只是不能通过该引用修改,但可能有其他引用可以修改
- 可变集合继承了只读集合的所有操作,并添加了写操作
- 使用
val声明可变集合,可以保护引用不被重新赋值,但仍可以修改集合内容
List(列表)
List 是有序集合,可以通过索引访问元素,允许重复元素。
创建 List
fun main() {
// 只读 List
val list1 = listOf(1, 2, 3, 4, 5)
val list2 = listOf<String>("a", "b", "c")
// 空列表
val emptyList = emptyList<Int>()
// 可变 List
val mutableList = mutableListOf(1, 2, 3)
// 使用 List 构造函数
val generatedList = List(5) { index -> index * 2 } // [0, 2, 4, 6, 8]
// ArrayList(Java 兼容)
val arrayList = arrayListOf(1, 2, 3)
println(list1) // [1, 2, 3, 4, 5]
println(generatedList) // [0, 2, 4, 6, 8]
}
访问元素
fun main() {
val list = listOf("a", "b", "c", "d", "e")
// 通过索引访问(可能越界)
println(list[0]) // a
println(list.get(2)) // c
// 安全访问(不会越界)
println(list.getOrNull(10)) // null
println(list.getOrElse(10) { "默认值" }) // 默认值
// 获取首尾元素
println(list.first()) // a
println(list.last()) // e
println(list.firstOrNull()) // a
println(list.lastOrNull()) // e
// 条件查找
println(list.first { it > "b" }) // c
println(list.last { it < "d" }) // c
// 查找索引
println(list.indexOf("c")) // 2
println(list.lastIndexOf("c")) // 2
println(list.indexOfFirst { it == "b" }) // 1
println(list.indexOfLast { it == "d" }) // 3
}
修改 List
fun main() {
val list = mutableListOf(1, 2, 3, 4, 5)
// 添加元素
list.add(6) // 末尾添加
list.add(0, 0) // 指定位置插入
list.addAll(listOf(7, 8, 9)) // 添加集合
// 删除元素
list.remove(3) // 删除元素值 3
list.removeAt(0) // 删除索引 0 的元素
list.removeAll(listOf(7, 8)) // 删除匹配集合中的元素
list.retainAll(listOf(1, 2, 5)) // 只保留指定元素
// 修改元素
list[0] = 100
list.set(1, 200)
// 批量操作
list.fill(0) // 所有元素填充为 0
// 清空
// list.clear()
println(list)
}
List 切片
fun main() {
val list = listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
// subList:获取子列表
println(list.subList(2, 5)) // [2, 3, 4]
// slice:按索引切片
println(list.slice(2..5)) // [2, 3, 4, 5]
println(list.slice(listOf(1, 3, 5))) // [1, 3, 5]
// take/drop:从开头取/丢弃
println(list.take(3)) // [0, 1, 2]
println(list.takeLast(3)) // [7, 8, 9]
println(list.drop(3)) // [3, 4, 5, 6, 7, 8, 9]
println(list.dropLast(3)) // [0, 1, 2, 3, 4, 5, 6]
// 条件取/弃
println(list.takeWhile { it < 5 }) // [0, 1, 2, 3, 4]
println(list.dropWhile { it < 5 }) // [5, 6, 7, 8, 9]
}
Set(集合)
Set 是不包含重复元素的无序集合。
创建 Set
fun main() {
// 只读 Set
val set1 = setOf(1, 2, 3)
val set2 = setOf(1, 2, 2, 3) // 自动去重
println(set2) // [1, 2, 3]
// 空集合
val emptySet = emptySet<Int>()
// 可变 Set
val mutableSet = mutableSetOf(1, 2, 3)
// 不同实现
val hashSet = hashSetOf(1, 2, 3) // 哈希表实现,不保证顺序
val linkedSet = linkedSetOf(1, 2, 3) // 保持插入顺序
val sortedSet = sortedSetOf(3, 1, 2) // 排序
println(hashSet) // [1, 2, 3](顺序不确定)
println(linkedSet) // [1, 2, 3]
println(sortedSet) // [1, 2, 3]
}
Set 操作
fun main() {
val set1 = setOf(1, 2, 3, 4)
val set2 = setOf(3, 4, 5, 6)
// 并集
println(set1.union(set2)) // [1, 2, 3, 4, 5, 6]
println(set1 + set2) // [1, 2, 3, 4, 5, 6]
// 交集
println(set1.intersect(set2)) // [3, 4]
println(set1 - (set1 - set2)) // [3, 4](另一种方式)
// 差集
println(set1.subtract(set2)) // [1, 2]
println(set1 - set2) // [1, 2]
// 对称差集(并集减交集)
println((set1 + set2) - (set1 intersect set2)) // [1, 2, 5, 6]
// 成员检查
println(3 in set1) // true
println(3 !in set1) // false
}
可变 Set 操作
fun main() {
val set = mutableSetOf(1, 2, 3)
// 添加
set.add(4)
set.addAll(setOf(5, 6))
// 删除
set.remove(1)
set.removeAll(setOf(2, 3))
println(set) // [4, 5, 6]
}
Map(映射)
Map 存储键值对,键唯一,值可以重复。
创建 Map
fun main() {
// 只读 Map
val map1 = mapOf("a" to 1, "b" to 2, "c" to 3)
// 空映射
val emptyMap = emptyMap<String, Int>()
// 可变 Map
val mutableMap = mutableMapOf("a" to 1, "b" to 2)
// 不同实现
val hashMap = hashMapOf("a" to 1, "b" to 2) // 哈希表
val linkedMap = linkedMapOf("a" to 1, "b" to 2) // 保持插入顺序
val sortedMap = sortedMapOf("b" to 2, "a" to 1) // 按键排序
println(map1) // {a=1, b=2, c=3}
println(sortedMap) // {a=1, b=2}
}
访问 Map
fun main() {
val map = mapOf("name" to "张三", "age" to 25, "city" to "北京")
// 通过键访问
println(map["name"]) // 张三
println(map.get("name")) // 张三
println(map["email"]) // null(键不存在)
// 安全访问
println(map.getOrDefault("email", "未设置")) // 未设置
println(map.getOrElse("email") { "无" }) // 无
println(map.getValue("name")) // 张三(键不存在时抛出异常)
// 检查键/值
println(map.containsKey("name")) // true
println("name" in map) // true
println(map.containsValue("张三")) // true
// 获取所有键/值
println(map.keys) // [name, age, city]
println(map.values) // [张三, 25, 北京]
println(map.entries) // [name=张三, age=25, city=北京]
}
修改 Map
fun main() {
val map = mutableMapOf("a" to 1, "b" to 2)
// 添加/修改
map["c"] = 3
map.put("d", 4)
map.putAll(mapOf("e" to 5, "f" to 6))
map["a"] = 100 // 修改已有键的值
// 条件添加
map.putIfAbsent("a", 1) // 键存在,不覆盖
map.putIfAbsent("g", 7) // 键不存在,添加
// 删除
map.remove("b")
map.remove("c", 3) // 仅当值匹配时删除
// 条件删除
map.keys.removeIf { it == "d" }
map.values.removeIf { it == 100 }
// 清空
// map.clear()
println(map)
}
Map 遍历
fun main() {
val map = mapOf("a" to 1, "b" to 2, "c" to 3)
// 遍历键值对
for ((key, value) in map) {
println("$key -> $value")
}
// forEach
map.forEach { (key, value) ->
println("$key = $value")
}
// 遍历 entries
for (entry in map.entries) {
println("${entry.key}: ${entry.value}")
}
// 只遍历键或值
map.keys.forEach { println(it) }
map.values.forEach { println(it) }
}
集合转换操作
map - 映射转换
map 将集合中的每个元素转换为新的元素:
fun main() {
val numbers = listOf(1, 2, 3, 4, 5)
// 基本映射
println(numbers.map { it * 2 }) // [2, 4, 6, 8, 10]
// 带索引映射
println(numbers.mapIndexed { index, value ->
"索引$index: $value"
}) // [索引0: 1, 索引1: 2, 索引2: 3, 索引3: 4, 索引4: 5]
// 过滤 null 结果
val withNulls = listOf(1, 2, null, 3)
println(withNulls.mapNotNull { it?.times(2) }) // [2, 4, 6]
// Map 的键/值转换
val map = mapOf("a" to 1, "b" to 2)
println(map.mapKeys { it.key.uppercase() }) // {A=1, B=2}
println(map.mapValues { it.value * 10 }) // {a=10, b=20}
}
filter - 过滤
filter 保留满足条件的元素:
fun main() {
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// 基本过滤
println(numbers.filter { it > 5 }) // [6, 7, 8, 9, 10]
println(numbers.filterNot { it > 5 }) // [1, 2, 3, 4, 5]
// 带索引过滤
println(numbers.filterIndexed { index, _ -> index % 2 == 0 }) // [1, 3, 5, 7, 9]
// 过滤非 null
val withNulls = listOf(1, null, 2, null, 3)
println(withNulls.filterNotNull()) // [1, 2, 3]
// 类型过滤
val mixed: List<Any> = listOf(1, "a", 2, "b", 3.0)
println(mixed.filterIsInstance<String>()) // [a, b]
println(mixed.filterIsInstance<Int>()) // [1, 2]
// partition:分离为两个列表
val (even, odd) = numbers.partition { it % 2 == 0 }
println("偶数: $even") // [2, 4, 6, 8, 10]
println("奇数: $odd") // [1, 3, 5, 7, 9]
}
flatMap - 扁平化
flatMap 先映射再扁平化:
fun main() {
// 将嵌套集合扁平化
val nested = listOf(listOf(1, 2), listOf(3, 4), listOf(5, 6))
println(nested.flatten()) // [1, 2, 3, 4, 5, 6]
// flatMap:映射 + 扁平化
val words = listOf("Hello", "World")
println(words.flatMap { it.toList() }) // [H, e, l, l, o, W, o, r, l, d]
// 生成多个元素
println(listOf(1, 2, 3).flatMap { listOf(it, it * 10) }) // [1, 10, 2, 20, 3, 30]
// 实际应用:处理数据类的集合属性
data class Student(val name: String, val courses: List<String>)
val students = listOf(
Student("张三", listOf("数学", "英语")),
Student("李四", listOf("物理", "化学", "数学"))
)
val allCourses = students.flatMap { it.courses }.distinct()
println(allCourses) // [数学, 英语, 物理, 化学]
}
associate - 关联转换
associate 系列函数将集合转换为 Map:
fun main() {
val words = listOf("apple", "banana", "cherry")
// associateWith:元素作为键
println(words.associateWith { it.length })
// {apple=5, banana=6, cherry=6}
// associateBy:元素作为值
println(words.associateBy { it.first() })
// {a=apple, b=banana, c=cherry}
// associateBy 自定义键和值
println(words.associateBy(
keySelector = { it.first().uppercase() },
valueTransform = { it.length }
)) // {A=5, B=6, C=6}
// associate:完全自定义
println(words.associate { it.uppercase() to it.length })
// {APPLE=5, BANANA=6, CHERRY=6}
}
groupBy - 分组
groupBy 按条件将元素分组:
fun main() {
val words = listOf("apple", "banana", "apricot", "blueberry", "cherry")
// 按首字母分组
println(words.groupBy { it.first() })
// {a=[apple, apricot], b=[banana, blueberry], c=[cherry]}
// 分组并转换值
println(words.groupBy(
keySelector = { it.first() },
valueTransform = { it.length }
)) // {a=[5, 7], b=[6, 9], c=[6]}
// 统计每组的数量
println(words.groupingBy { it.first() }.eachCount())
// {a=2, b=2, c=1}
// 按条件分组
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
println(numbers.groupBy { if (it % 2 == 0) "偶数" else "奇数" })
// {奇数=[1, 3, 5, 7, 9], 偶数=[2, 4, 6, 8, 10]}
}
聚合操作
基本聚合
fun main() {
val numbers = listOf(1, 2, 3, 4, 5)
// 计数
println(numbers.count()) // 5
println(numbers.count { it > 3 }) // 2
// 求和
println(numbers.sum()) // 15
println(numbers.sumOf { it * 2 }) // 30
// 平均值
println(numbers.average()) // 3.0
// 最大最小
println(numbers.maxOrNull()) // 5
println(numbers.minOrNull()) // 1
println(numbers.maxByOrNull { it % 3 }) // 5
// 字符串连接
println(numbers.joinToString()) // 1, 2, 3, 4, 5
println(numbers.joinToString("|", "[", "]")) // [1|2|3|4|5]
println(numbers.joinToString(limit = 3, truncated = "...")) // 1, 2, 3, ...
}
reduce 和 fold
fun main() {
val numbers = listOf(1, 2, 3, 4, 5)
// reduce:从第一个元素开始累加
val sum = numbers.reduce { acc, num -> acc + num }
println(sum) // 15
// fold:指定初始值
val sumWithInitial = numbers.fold(10) { acc, num -> acc + num }
println(sumWithInitial) // 25
// 从右向左
val rightSum = numbers.reduceRight { num, acc -> num + acc }
println(rightSum) // 15
// 累积字符串
val concatenated = numbers.fold("") { acc, num -> "$acc$num" }
println(concatenated) // 12345
// 计算阶乘
val factorial = (1..5).fold(1) { acc, n -> acc * n }
println(factorial) // 120
}
查找操作
fun main() {
val numbers = listOf(1, 2, 3, 4, 5, 4, 3, 2, 1)
// 查找元素
println(numbers.find { it > 3 }) // 4(第一个)
println(numbers.findLast { it > 3 }) // 4(最后一个)
// 查找所有匹配
println(numbers.filter { it > 3 }) // [4, 5, 4]
// 包含检查
println(numbers.contains(3)) // true
println(3 in numbers) // true
println(numbers.containsAll(listOf(1, 2))) // true
// 检查条件
println(numbers.any { it > 5 }) // false(是否有满足条件的)
println(numbers.all { it > 0 }) // true(是否全部满足)
println(numbers.none { it < 0 }) // true(是否都不满足)
}
排序操作
fun main() {
val numbers = listOf(3, 1, 4, 1, 5, 9, 2, 6)
// 升序排序
println(numbers.sorted()) // [1, 1, 2, 3, 4, 5, 6, 9]
// 降序排序
println(numbers.sortedDescending()) // [9, 6, 5, 4, 3, 2, 1, 1]
// 按条件排序
data class Person(val name: String, val age: Int)
val people = listOf(
Person("张三", 25),
Person("李四", 20),
Person("王五", 30)
)
println(people.sortedBy { it.age })
// [Person(name=李四, age=20), Person(name=张三, age=25), Person(name=王五, age=30)]
println(people.sortedByDescending { it.age })
// [Person(name=王五, age=30), Person(name=张三, age=25), Person(name=李四, age=20)]
// 多条件排序
println(people.sortedWith(compareBy({ it.age }, { it.name })))
// 反转
println(numbers.reversed()) // [6, 2, 9, 5, 1, 4, 1, 3]
// 随机打乱
println(numbers.shuffled()) // 随机顺序
}
Sequence(序列)
Sequence 是一种惰性求值的集合,适合处理大数据集,可以避免创建中间集合。
创建 Sequence
fun main() {
// 从元素创建
val seq1 = sequenceOf(1, 2, 3, 4, 5)
// 从 Iterable 转换
val seq2 = listOf(1, 2, 3).asSequence()
// 使用 generateSequence
val seq3 = generateSequence(1) { it + 1 } // 无限序列
.take(5) // 取前 5 个
println(seq3.toList()) // [1, 2, 3, 4, 5]
// 使用 sequence 构建器
val seq4 = sequence {
yield(1)
yieldAll(listOf(2, 3, 4))
yield(5)
}
println(seq4.toList()) // [1, 2, 3, 4, 5]
}
惰性求值的优势
fun main() {
// List:立即执行所有操作
val listResult = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
.map {
println("List map: $it")
it * 2
}
.filter {
println("List filter: $it")
it > 10
}
.take(2)
println("List 结果: $listResult")
println()
// Sequence:惰性执行,按需计算
val seqResult = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
.asSequence()
.map {
println("Seq map: $it")
it * 2
}
.filter {
println("Seq filter: $it")
it > 10
}
.take(2)
.toList()
println("Seq 结果: $seqResult")
}
执行对比:
- List 方式:先对所有元素执行 map,再对所有元素执行 filter
- Sequence 方式:对每个元素依次执行 map → filter → take,直到取够 2 个元素
Sequence 适用场景
fun main() {
// 处理大文件或数据流
val largeData = generateSequence(1) { it + 1 }
.take(1_000_000)
.filter { it % 2 == 0 }
.map { it * 2 }
.take(10)
.toList()
println(largeData) // [4, 8, 12, 16, 20, 24, 28, 32, 36, 40]
// 注意:Sequence 只能遍历一次
val seq = sequenceOf(1, 2, 3)
seq.forEach { print("$it ") } // 1 2 3
// seq.forEach { print("$it ") } // 再次遍历:无输出(已消费)
}
集合与数组互转
fun main() {
// List 转 Array
val list = listOf(1, 2, 3)
val intArray = list.toIntArray()
val typedArray = list.toTypedArray()
// Array 转 List
val array = intArrayOf(1, 2, 3)
val listFromArray = array.toList()
val mutableListFromArray = array.toMutableList()
// Set 与 List 互转
val set = setOf(1, 2, 3)
val listFromSet = set.toList()
val setFromList = listOf(1, 1, 2, 2, 3).toSet()
println(setFromList) // [1, 2, 3]
// Map 与 List 互转
val map = mapOf("a" to 1, "b" to 2)
val entries = map.entries.toList()
val keys = map.keys.toList()
val values = map.values.toList()
}
小结
本章我们学习了 Kotlin 集合的核心内容:
- List:有序可重复集合,支持索引访问
- Set:唯一元素集合,支持集合运算
- Map:键值对集合,键唯一
- 转换操作:
map、filter、flatMap、associate、groupBy - 聚合操作:
sum、average、reduce、fold - 排序操作:
sorted、sortedBy、reversed - Sequence:惰性求值,适合大数据处理
Kotlin 的集合操作函数让数据处理变得非常简洁,掌握这些操作对于编写高效的 Kotlin 代码至关重要。
练习
- 使用
filter和map筛选出 1-100 中所有偶数的平方 - 使用
groupBy将字符串列表按首字母分组 - 使用
reduce或fold计算列表的乘积 - 使用
associate将姓名列表转换为"姓名 → 长度"的 Map - 对比 List 和 Sequence 在处理大数据时的性能差异