跳到主要内容

Kotlin 集合

集合是 Kotlin 中存储和操作数据组的核心工具。Kotlin 标准库提供了丰富的集合类型和操作函数,让数据处理变得简洁高效。本章将详细介绍 List、Set、Map 以及各种操作函数。

集合类型概述

Kotlin 提供三种基本集合类型,每种都有只读和可变两种形式:

类型描述只读可变
List有序集合,允许重复元素ListMutableList
Set唯一元素集合SetMutableSet
Map键值对集合MapMutableMap

集合继承体系

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 集合的核心内容:

  1. List:有序可重复集合,支持索引访问
  2. Set:唯一元素集合,支持集合运算
  3. Map:键值对集合,键唯一
  4. 转换操作mapfilterflatMapassociategroupBy
  5. 聚合操作sumaveragereducefold
  6. 排序操作sortedsortedByreversed
  7. Sequence:惰性求值,适合大数据处理

Kotlin 的集合操作函数让数据处理变得非常简洁,掌握这些操作对于编写高效的 Kotlin 代码至关重要。

练习

  1. 使用 filtermap 筛选出 1-100 中所有偶数的平方
  2. 使用 groupBy 将字符串列表按首字母分组
  3. 使用 reducefold 计算列表的乘积
  4. 使用 associate 将姓名列表转换为"姓名 → 长度"的 Map
  5. 对比 List 和 Sequence 在处理大数据时的性能差异