Kotlin 集合与序列
问题
Kotlin 集合框架有哪些类型?Collection 和 Sequence 有什么区别?
答案
1. 集合类型体系
Kotlin 集合分为只读集合和可变集合两种:
| 类型 | 只读 | 可变 | 底层实现 |
|---|---|---|---|
| List | listOf() | mutableListOf() | ArrayList |
| Set | setOf() | mutableSetOf() | LinkedHashSet |
| Map | mapOf() | mutableMapOf() | LinkedHashMap |
// 只读集合 —— 编译时阻止修改
val list = listOf(1, 2, 3)
// list.add(4) // ❌ 编译错误
// 可变集合
val mutableList = mutableListOf(1, 2, 3)
mutableList.add(4) // ✅
// 不可变 → 可变
val editable = list.toMutableList()
// 空集合需要指定类型
val emptyList = emptyList<String>()
只读 ≠ 不可变
listOf() 返回的是只读视图(编译时限制),底层仍是 ArrayList。通过类型转换可以修改:
val list = listOf(1, 2, 3)
(list as MutableList).add(4) // ⚠️ 运行时可以修改(不推荐!)
真正的不可变集合需要使用 Kotlinx Immutable Collections 库。
2. 集合操作符
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// 过滤
numbers.filter { it % 2 == 0 } // [2, 4, 6, 8, 10]
numbers.filterNot { it % 2 == 0 } // [1, 3, 5, 7, 9]
// 变换
numbers.map { it * 2 } // [2, 4, 6, 8, ...]
numbers.flatMap { listOf(it, it * 10) } // [1, 10, 2, 20, ...]
// 聚合
numbers.sum() // 55
numbers.reduce { acc, i -> acc + i } // 55
numbers.fold(100) { acc, i -> acc + i } // 155
// 分组
numbers.groupBy { if (it % 2 == 0) "even" else "odd" }
// {odd=[1, 3, 5, 7, 9], even=[2, 4, 6, 8, 10]}
// 排序
numbers.sortedDescending() // [10, 9, 8, ...]
numbers.sortedBy { -it } // 等价
// 判断
numbers.any { it > 5 } // true
numbers.all { it > 0 } // true
numbers.none { it > 10 } // true
// 查找
numbers.first { it > 5 } // 6
numbers.firstOrNull { it > 100 } // null
3. Collection vs Sequence
这是面试核心考点 — 即时求值 vs 惰性求值:
// Collection —— 每一步都创建中间集合
listOf(1, 2, 3, 4, 5)
.map { println("map $it"); it * 2 } // 先全部 map,创建新 List
.filter { println("filter $it"); it > 4 } // 再全部 filter,创建新 List
// 输出:map 1, map 2, map 3, map 4, map 5, filter 2, filter 4, filter 6, filter 8, filter 10
// Sequence —— 逐个元素通过所有操作
listOf(1, 2, 3, 4, 5)
.asSequence()
.map { println("map $it"); it * 2 } // 惰性,不立即执行
.filter { println("filter $it"); it > 4 } // 惰性,不立即执行
.toList() // 终端操作时才开始执行
// 输出:map 1, filter 2, map 2, filter 4, map 3, filter 6, ...(逐个元素)
| 特性 | Collection | Sequence |
|---|---|---|
| 求值时机 | 即时(每步立即执行) | 惰性(终端操作时执行) |
| 中间集合 | 每步创建新集合 | 不创建中间集合 |
| 处理顺序 | 横向(一步处理所有元素) | 纵向(一个元素通过所有步骤) |
| 短路优化 | ❌ | ✅(first/take 可提前终止) |
| 适用场景 | 小集合、简单操作 | 大集合、多步链式操作 |
使用 Sequence 的时机
- 集合元素 > 10000 或操作链 > 3 步 时考虑 Sequence
- 需要
first、take等短路操作时使用 Sequence - 小集合用 Collection 即可,Sequence 有额外开销(Iterator 对象、装箱)
4. Map 操作
val map = mapOf("a" to 1, "b" to 2, "c" to 3)
// 遍历
map.forEach { (key, value) -> println("$key -> $value") }
// 过滤
map.filter { (_, value) -> value > 1 } // {b=2, c=3}
map.filterKeys { it != "a" } // {b=2, c=3}
// 变换
map.mapValues { (_, v) -> v * 2 } // {a=2, b=4, c=6}
map.mapKeys { (k, _) -> k.uppercase() } // {A=1, B=2, C=3}
// 安全获取
map.getOrDefault("d", 0) // 0
map.getOrElse("d") { computeDefault() }
// 可变 Map 的 getOrPut
val cache = mutableMapOf<String, Int>()
cache.getOrPut("key") { expensiveCalculation() }
常见面试问题
Q1: Sequence 为什么比 Collection 快?一定更快吗?
答案:
Sequence 在多步链式操作 + 大集合的场景下更快,原因:
- 不创建中间集合 — 减少内存分配和 GC 压力
- 短路优化 —
first()、take()找到结果后立即停止
但 Sequence 不一定更快:
- 小集合(
< 几十个)Collection 可能更快,因为 Sequence 有 Iterator 开销 - 单步操作(如只有一个
map)Collection 更直接 - 需要索引访问的操作不适合 Sequence
Q2: listOf() 和 arrayListOf() 的区别?
答案:
listOf()→ 返回List<T>(只读),底层是 Arrays.asList 或 EmptyListarrayListOf()→ 返回ArrayList<T>(可变),等价于mutableListOf()
推荐优先使用 listOf() + mutableListOf(),接口更清晰。
Q3: map vs flatMap 的区别?
答案:
val list = listOf(listOf(1, 2), listOf(3, 4))
list.map { it } // [[1, 2], [3, 4]] —— 保持嵌套结构
list.flatMap { it } // [1, 2, 3, 4] —— 展平一层
// flatMap = map + flatten
list.map { it }.flatten() == list.flatMap { it } // true
flatMap 将每个元素映射为一个集合,然后把所有集合展平为一个。
Q4: 如何在 Kotlin 中实现线程安全的集合?
答案:
// 1. Collections.synchronizedList(简单但粗粒度)
val syncList = Collections.synchronizedList(mutableListOf<String>())
// 2. ConcurrentHashMap(高性能并发 Map)
val concurrentMap = ConcurrentHashMap<String, Int>()
// 3. CopyOnWriteArrayList(读多写少)
val cowList = CopyOnWriteArrayList<String>()
// 4. 协程 + Mutex(Kotlin 风格)
val mutex = Mutex()
val list = mutableListOf<String>()
mutex.withLock { list.add("item") }
Q5: associate、groupBy、partition 的区别?
答案:
val list = listOf("apple", "banana", "avocado", "blueberry")
// associate —— 一对一映射为 Map(key 冲突时后者覆盖前者)
list.associateBy { it.first() }
// {a=avocado, b=blueberry}
// groupBy —— 一对多分组为 Map<K, List<V>>
list.groupBy { it.first() }
// {a=[apple, avocado], b=[banana, blueberry]}
// partition —— 按条件分为两组 Pair<List, List>
val (aWords, others) = list.partition { it.startsWith("a") }
// aWords=[apple, avocado], others=[banana, blueberry]