跳到主要内容

Kotlin 集合与序列

问题

Kotlin 集合框架有哪些类型?Collection 和 Sequence 有什么区别?

答案

1. 集合类型体系

Kotlin 集合分为只读集合可变集合两种:

类型只读可变底层实现
ListlistOf()mutableListOf()ArrayList
SetsetOf()mutableSetOf()LinkedHashSet
MapmapOf()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, ...(逐个元素)
特性CollectionSequence
求值时机即时(每步立即执行)惰性(终端操作时执行)
中间集合每步创建新集合不创建中间集合
处理顺序横向(一步处理所有元素)纵向(一个元素通过所有步骤)
短路优化✅(first/take 可提前终止)
适用场景小集合、简单操作大集合、多步链式操作
使用 Sequence 的时机
  • 集合元素 > 10000 或操作链 > 3 步 时考虑 Sequence
  • 需要 firsttake短路操作时使用 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 在多步链式操作 + 大集合的场景下更快,原因:

  1. 不创建中间集合 — 减少内存分配和 GC 压力
  2. 短路优化first()take() 找到结果后立即停止

但 Sequence 不一定更快

  • 小集合< 几十个)Collection 可能更快,因为 Sequence 有 Iterator 开销
  • 单步操作(如只有一个 map)Collection 更直接
  • 需要索引访问的操作不适合 Sequence

Q2: listOf()arrayListOf() 的区别?

答案

  • listOf() → 返回 List<T>(只读),底层是 Arrays.asList 或 EmptyList
  • arrayListOf() → 返回 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: associategroupBypartition 的区别?

答案

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]

相关链接