Kotlin: Kotlin集合操作详解
最后更新:2026-08-26
Kotlin 的集合操作让 Charlie 用声明式流水线替代命令式循环—— 一行代码代替 15 行 for 循环,且意图更清晰。
1. 你将学到
- / / / / 核心操作符
- 惰性序列 : 避免中间集合分配
- 聚合: / / /
- 不可变 vs 可变: vs
- Charlie 实战:处理百万订单的函数式流水线
2. 一个架构师的真实故事
(1) 痛点:百万级订单处理内存溢出
Charlie 用 处理百万级订单数据,每个中间操作都创建新集合,内存占用 3GB 导致 OOM。
(2) Sequence 惰性求值的解法
KOTLIN
// Eager: creates intermediate collections at each step
orders.map { enrich(it) } // Collection 1: 1M elements
.filter { it.total > 100 } // Collection 2: ~500K elements
.toList() // Collection 3
// Lazy: processes one element through entire pipeline
orders.asSequence()
.map { enrich(it) }
.filter { it.total > 100 }
.toList() // Only 1 final collection
Sequence 像流水线:每个元素走完全程才处理下一个,无需中间集合。
3. 核心操作符
(1) map — 转换
KOTLIN
val orders = listOf(
Order("ORD-001", 299.99, "Alice"),
Order("ORD-002", 1_500.00, "Bob")
)
// Transform each element
val ids = orders.map { it.id } // [ORD-001, ORD-002]
val summaries = orders.map { "${it.id}: \$${it.total} USD" }
// mapIndexed: with index
orders.mapIndexed { i, order -> "[${i + 1}] ${order.id}" }
// mapNotNull: transform + filter nulls
val emails = orders.mapNotNull { it.customerEmail }
(2) filter — 过滤
KOTLIN
// Filter by predicate
val highValue = orders.filter { it.total > 1_000 }
val pending = orders.filter { it.status == "PENDING" }
// filterNot: inverse filter
val active = orders.filterNot { it.status == "CANCELLED" }
// filterIndexed: with index
orders.filterIndexed { i, _ -> i % 2 == 0 } // Even-indexed orders
(3) flatMap — 展平转换
KOTLIN
data class Customer(val name: String, val orders: List`<Order>`)
val customers = listOf(
Customer("Alice", listOf(Order("ORD-001", 299.99), Order("ORD-002", 150.0))),
Customer("Bob", listOf(Order("ORD-003", 1_500.00)))
)
// Map + Flatten in one step
val allOrders = customers.flatMap { it.orders }
// [Order(ORD-001, 299.99), Order(ORD-002, 150.0), Order(ORD-003, 1500.0)]
(4) groupBy — 分组
KOTLIN
// Group by key
val byCustomer: Map<String, List`<Order>`> = orders.groupBy { it.customer }
// Group by with value transform
val totalsByCustomer = orders.groupBy(
keySelector = { it.customer },
valueTransform = { it.total }
)
// {Alice=[299.99], Bob=[1500.0]}
(5) associate — 关联映射
KOTLIN
// Create map from list
val orderMap = orders.associate { it.id to it }
// {ORD-001=Order(...), ORD-002=Order(...)}
// associateBy: key selector
val byId = orders.associateBy { it.id }
// associateBy with value transform
val totalsById = orders.associateBy(
keySelector = { it.id },
valueTransform = { it.total }
)
(6) 操作符速查表
| 操作符 | 功能 | 输入→输出 | 类比 SQL |
|---|---|---|---|
| 转换 | → | SELECT | |
| 过滤 | → | WHERE | |
| 展平转换 | → | JOIN + SELECT | |
| 分组 | → <T> |
GROUP BY | |
| 关联映射 | → | - | |
| 去重 | → | DISTINCT | |
| 排序 | → | ORDER BY |
4. 集合操作流水线
flowchart LR
A[Orders<br/>1M records] --> B[filter<br/>total > 1000]
B --> C[map<br/>extract customer]
C --> D[distinct<br/>unique customers]
D --> E[groupBy<br/>by region]
E --> F[Result<br/>Map of customers]
5. Sequence 惰性求值
(1) Eager vs Lazy
KOTLIN
// Eager (List): each step creates new collection
val result = orders
.map { println("map: ${it.id}"); it.copy(total = it.total * 0.9) }
.filter { println("filter: ${it.id}"); it.total > 100 }
.take(2)
.toList()
// Lazy (Sequence): processes one element at a time
val result2 = orders.asSequence()
.map { println("seq-map: ${it.id}"); it.copy(total = it.total * 0.9) }
.filter { println("seq-filter: ${it.id}"); it.total > 100 }
.take(2)
.toList()
// Sequence only processes elements until take(2) is satisfied
(2) 何时使用 Sequence
| 场景 | 用 List | 用 Sequence |
|---|---|---|
| 数据量 | < 10,000 | > 10,000 |
| 操作步数 | 1-2 步 | 3+ 步 |
| 中间结果大小 | 与原集合相近 | 显著缩小 |
| 是否需要多次遍历 | 是 | 否 |
(3) List vs Sequence 性能对比
| 维度 | List(Eager) | Sequence(Lazy) |
|---|---|---|
| 中间集合 | 每步创建 | 不创建 |
| 内存 | O(n × 步数) | O(1) |
| 短路操作 | 不优化 | 优化(如 只处理 2 个) |
| 首次结果 | 等全部处理完 | 立即可用 |
6. 聚合操作
(1) fold / reduce
KOTLIN
// fold: with initial value
val totalRevenue = orders.fold(0.0) { acc, order -> acc + order.total }
// reduce: first element as initial value
val maxOrder = orders.reduce { max, order ->
if (order.total > max.total) order else max
}
// foldRight: from end to start
val reversed = orders.foldRight(emptyList`<Order>`()) { order, acc -> acc + order }
(2) 便捷聚合
KOTLIN
val total = orders.sumOf { it.total }
val avg = orders.map { it.total }.average()
val max = orders.maxByOrNull { it.total }
val min = orders.minByOrNull { it.total }
val count = orders.count { it.total > 1_000 }
// Sorting
val sorted = orders.sortedByDescending { it.total }
val top3 = orders.sortedByDescending { it.total }.take(3)
(3) fold vs reduce 对比
| 维度 | ||
|---|---|---|
| 初始值 | 必须提供 | 隐式用第一个元素 |
| 空集合 | 安全(返回初始值) | 抛异常 |
| 返回类型 | 可与元素类型不同 | 与元素类型相同 |
| 推荐度 | ⭐⭐⭐ | ⭐⭐ |
7. 不可变 vs 可变集合
(1) 只读与可变接口
KOTLIN
// Read-only (immutable interface)
val list: List`<Order>` = listOf(Order("ORD-001", 299.99, "Alice"))
// Mutable
val mutableList: MutableList`<Order>` = mutableListOf()
mutableList.add(Order("ORD-002", 1_500.00, "Bob"))
// Read-only view of mutable list
val readOnly: List`<Order>` = mutableList // OK: MutableList extends List
// readOnly.add(...) // ERROR: List has no add method
mutableList.add(Order("ORD-003", 45.50, "Charlie")) // Changes readOnly view!
(2) 防御性拷贝
KOTLIN
class OrderProcessor(private val _orders: MutableList`<Order>`) {
// Defensive copy: expose immutable view
val orders: List`<Order>` get() = _orders.toList()
// Or use immutable view (no copy, but can be cast back)
val ordersView: List`<Order>` get() = _orders.toList()
}
(3) 集合类型对比
| 类型 | 只读接口 | 可变接口 | 工厂函数 |
|---|---|---|---|
| List | / | ||
| Set | / | ||
| Map | / |
8. 完整示例:百万订单处理流水线
KOTLIN
// ============================================
// OrderProcessor - Collection Pipeline
// Feature: Process orders with functional operators
// ============================================
data class Order(val id: String, val total: Double, val status: String, val customer: String, val region: String)
fun main() {
// Simulate order data
val orders = listOf(
Order("ORD-001", 299.99, "CONFIRMED", "Alice", "US"),
Order("ORD-002", 15_000.00, "CONFIRMED", "Bob", "EU"),
Order("ORD-003", 2_500.00, "PENDING", "Charlie", "US"),
Order("ORD-004", 45.50, "CANCELLED", "Alice", "ASIA"),
Order("ORD-005", 8_900.00, "CONFIRMED", "Bob", "EU"),
Order("ORD-006", 1_200.00, "SHIPPED", "Charlie", "US"),
Order("ORD-007", 350.00, "CONFIRMED", "Alice", "ASIA"),
Order("ORD-008", 22_000.00, "PENDING", "Bob", "EU"),
Order("ORD-009", 750.00, "CONFIRMED", "Charlie", "US"),
Order("ORD-010", 4_500.00, "SHIPPED", "Alice", "US")
)
// Pipeline 1: High-value confirmed orders
println("=== High-Value Confirmed Orders ===")
orders.filter { it.status == "CONFIRMED" && it.total > 1_000 }
.sortedByDescending { it.total }
.forEach { println(" ${it.id}: \$${it.total} USD (${it.customer})") }
// Pipeline 2: Revenue by region
println("\n=== Revenue by Region ===")
orders.filter { it.status != "CANCELLED" }
.groupBy { it.region }
.mapValues { (_, list) -> list.sumOf { it.total } }
.forEach { (region, revenue) -> println(" $region: \$$revenue USD") }
// Pipeline 3: Top customers by order count and revenue
println("\n=== Customer Summary ===")
orders.filter { it.status != "CANCELLED" }
.groupBy { it.customer }
.map { (customer, list) ->
val count = list.size
val total = list.sumOf { it.total }
val avg = list.map { it.total }.average()
"$customer: $count orders, \$${total} USD total, \$${"%.2f".format(avg)} avg"
}
.forEach { println(" $it") }
// Pipeline 4: Using Sequence for efficient processing
println("\n=== Top 3 Orders (Sequence) ===")
orders.asSequence()
.filter { it.status != "CANCELLED" }
.sortedByDescending { it.total }
.take(3)
.forEach { println(" ${it.id}: \$${it.total} USD") }
// Aggregate: fold to build a summary string
val summary = orders
.filter { it.status != "CANCELLED" }
.fold("Order Summary: ") { acc, order -> "$acc\n ${order.id} (\$${order.total} USD)" }
println("\n${summary}")
println("Total orders: ${orders.size}, Active: ${orders.count { it.status != "CANCELLED" }}")
}
输出:
TEXT
📖 仅展示
=== High-Value Confirmed Orders ===
ORD-005: $8900.0 USD (Bob)
ORD-002: $15000.0 USD (Bob)
=== Revenue by Region ===
US: $7599.99 USD
EU: $46400.0 USD
ASIA: $350.0 USD
=== Customer Summary ===
Alice: 2 orders, $3649.99 USD total, $1824.995 avg
Bob: 3 orders, $46400.0 USD total, $15466.666666666666 avg
Charlie: 3 orders, $4450.0 USD total, $1483.3333333333333 avg
=== Top 3 Orders (Sequence) ===
ORD-008: $22000.0 USD (Bob)
ORD-002: $15000.0 USD (Bob)
ORD-005: $8900.0 USD (Bob)
Order Summary:
ORD-001 ($299.99 USD)
ORD-002 ($15000.0 USD)
ORD-003 ($2500.0 USD)
ORD-005 ($8900.0 USD)
ORD-006 ($1200.0 USD)
ORD-007 ($350.0 USD)
ORD-008 ($22000.0 USD)
ORD-009 ($750.0 USD)
ORD-010 ($4500.0 USD)
Total orders: 10, Active: 9
❓ 常见问题
Q Kotlin 集合操作 vs Java Stream 有什么区别?
A Kotlin 集合操作更简洁( 关键字、尾 Lambda),不需要 和 转换,且 Sequence 类似 Stream 但更轻量。
Q 什么时候用 Sequence?
A 数据量大(>10,000)、操作步骤多(3+步)、或需要短路操作(//)时用 Sequence。小数据用 List 更简单。
Q 会复制数据吗?
A 会。 创建新列表,是防御性拷贝。如果只需要只读视图且不需要副本,直接用 接口引用即可。
Q 和 选哪个?
A 优先 fold,因为它可以指定初始值且对空集合安全。reduce 在空集合时会抛异常。
Q Kotlin 的 List 是真正不可变的吗?
A 不是。Kotlin 的 是只读接口(不可修改),但底层实现可能是可变的。真正不可变需要用 创建副本。
Q 和 + 有区别吗?
A 功能等价, 更高效(一步完成)。 是 + 的组合简写。
📖 小节
- 核心操作符:(转换)、(过滤)、(展平转换)、(分组)、(映射)
- Sequence 惰性求值避免中间集合分配,大数据量首选
- (有初始值,安全)优先于 (空集合不安全)
- 是只读接口, 是可变接口——优先使用只读
- 防御性拷贝 保护内部可变状态
- 集合操作流水线让数据处理代码声明式、可读、可组合
📝 作业
- 基础题(难度⭐):用 和 从订单列表中提取所有金额 > 1000 USD 的订单 ID。提示:
- 进阶题(难度⭐⭐):用 + 计算每个客户的总消费金额,并按金额降序排列。提示:
- 挑战题(难度⭐⭐⭐):用 实现百万级订单的惰性处理流水线:filter → map → take(100),打印实际处理的元素数量。提示:用 计数