Kotlin 语言变换函数 map
map 函数是 Kotlin 中最常用且强大的变换函数之一,它允许你对集合中的每个元素进行转换,并返回一个新的集合。
1. map 函数基础
基本语法
// 基本 map 函数
val numbers = listOf(1, 2, 3, 4, 5)
val doubled = numbers.map { it * 2 } // [2, 4, 6, 8, 10]
// 使用显式参数名
val squared = numbers.map { num -> num * num } // [1, 4, 9, 16, 25]
// 多行转换逻辑
val descriptions = numbers.map { number ->
when {
number % 2 == 0 -> "$number 是偶数"
else -> "$number 是奇数"
}
}
// 结果: ["1 是奇数", "2 是偶数", "3 是奇数", "4 是偶数", "5 是奇数"]
支持 map 的集合类型
// List
val list = listOf(1, 2, 3).map { it * 2 }
// Set
val set = setOf(1, 2, 3).map { it.toString() }
// Array
val array = arrayOf(1, 2, 3).map { it + 1 }
// Map (映射键或值)
val map = mapOf("a" to 1, "b" to 2, "c" to 3)
val keys = map.map { it.key } // ["a", "b", "c"]
val values = map.map { it.value } // [1, 2, 3]
// Range
val range = (1..5).map { it * 10 } // [10, 20, 30, 40, 50]
// Sequence (惰性计算)
val sequence = sequenceOf(1, 2, 3).map { it * 3 }
2. map 变体函数
mapIndexed - 带索引的映射
val numbers = listOf(10, 20, 30, 40, 50)
// 获取索引和值
val indexed = numbers.mapIndexed { index, value ->
"索引 $index: 值 $value"
}
// 结果: ["索引 0: 值 10", "索引 1: 值 20", ...]
// 使用索引进行计算
val shifted = numbers.mapIndexed { index, value ->
value + index
}
// 结果: [10, 21, 32, 43, 54]
// 处理二维数组
val matrix = listOf(
listOf(1, 2, 3),
listOf(4, 5, 6),
listOf(7, 8, 9)
)
val flatWithIndices = matrix.flatMapIndexed { rowIndex, row ->
row.mapIndexed { colIndex, value ->
Triple(rowIndex, colIndex, value)
}
}
// 结果: [(0,0,1), (0,1,2), (0,2,3), (1,0,4), ...]
mapNotNull - 过滤空值
val mixed = listOf("1", "2", "abc", "3", "def")
// 安全转换为 Int,过滤掉转换失败的元素
val numbers = mixed.mapNotNull { it.toIntOrNull() }
// 结果: [1, 2, 3]
// 结合复杂的转换逻辑
data class Person(val name: String, val age: Int?)
val people = listOf(
Person("Alice", 25),
Person("Bob", null),
Person("Charlie", 30),
Person("David", null)
)
val validAges = people.mapNotNull { person ->
person.age?.let { age ->
"${person.name}: $age 岁"
}
}
// 结果: ["Alice: 25 岁", "Charlie: 30 岁"]
mapIndexedNotNull - 带索引且过滤空值
val data = listOf("1", "two", "3", "four", "5")
val result = data.mapIndexedNotNull { index, value ->
value.toIntOrNull()?.let { intValue ->
"位置 $index: 数字 $intValue"
}
}
// 结果: ["位置 0: 数字 1", "位置 2: 数字 3", "位置 4: 数字 5"]
3. flatMap - 扁平化映射
基本用法
// 将嵌套列表展平
val nestedList = listOf(
listOf(1, 2, 3),
listOf(4, 5),
listOf(6, 7, 8, 9)
)
val flattened = nestedList.flatMap { it } // [1, 2, 3, 4, 5, 6, 7, 8, 9]
// 复杂转换并展平
val sentences = listOf(
"Hello world",
"Kotlin is awesome",
"Functional programming"
)
val words = sentences.flatMap { sentence ->
sentence.split(" ")
}
// 结果: ["Hello", "world", "Kotlin", "is", "awesome", "Functional", "programming"]
// 生成多个元素的映射
val numbers = listOf(1, 2, 3)
val expanded = numbers.flatMap { number ->
listOf(number, number * 10, number * 100)
}
// 结果: [1, 10, 100, 2, 20, 200, 3, 30, 300]
flatMap 与 map 的区别
// map: 一对一转换
val numbers = listOf(1, 2, 3)
val mapResult = numbers.map { listOf(it, it * 2) }
// 结果: [[1, 2], [2, 4], [3, 6]]
// flatMap: 展平嵌套结构
val flatMapResult = numbers.flatMap { listOf(it, it * 2) }
// 结果: [1, 2, 2, 4, 3, 6]
// 实际应用:一对多关系
data class User(val id: Int, val name: String, val roles: List<String>)
val users = listOf(
User(1, "Alice", listOf("Admin", "User")),
User(2, "Bob", listOf("User")),
User(3, "Charlie", listOf("Manager", "User", "Guest"))
)
// 获取所有不重复的角色
val allRoles = users.flatMap { it.roles }.toSet()
// 结果: ["Admin", "User", "Manager", "Guest"]
4. associate 系列 - 转换为 Map
associateBy - 根据键转换
data class Product(val id: Int, val name: String, val price: Double)
val products = listOf(
Product(1, "Laptop", 999.99),
Product(2, "Phone", 699.99),
Product(3, "Tablet", 399.99)
)
// 根据 id 创建映射
val byId = products.associateBy { it.id }
// 结果: Map<Int, Product>
// {1=Product(id=1, name=Laptop, price=999.99), ...}
// 根据 name 创建映射(注意:键不能重复)
val byName = products.associateBy { it.name }
// {Laptop=Product(...), Phone=Product(...), Tablet=Product(...)}
// 处理可能的键冲突
val productsWithSamePrice = listOf(
Product(1, "A", 100.0),
Product(2, "B", 100.0), // 相同的价格
Product(3, "C", 200.0)
)
val byPrice = productsWithSamePrice.associateBy(
keySelector = { it.price },
valueTransform = { it.name }
)
// 结果: {100.0="B", 200.0="C"} (键冲突时保留最后一个)
associateWith - 根据值转换
val keys = listOf("apple", "banana", "cherry")
// 创建键到默认值的映射
val withDefault = keys.associateWith { 0 }
// 结果: {"apple"=0, "banana"=0, "cherry"=0}
// 创建键到复杂值的映射
val fruits = listOf("Apple", "Banana", "Cherry")
val fruitInfo = fruits.associateWith { fruit ->
when (fruit) {
"Apple" -> "红色水果"
"Banana" -> "黄色水果"
"Cherry" -> "红色小水果"
else -> "未知水果"
}
}
// 结果: {"Apple"="红色水果", "Banana"="黄色水果", "Cherry"="红色小水果"}
associate - 自定义键值对转换
val numbers = listOf(1, 2, 3, 4, 5)
// 创建数字到其平方的映射
val squares = numbers.associate { number ->
number to (number * number)
}
// 结果: {1=1, 2=4, 3=9, 4=16, 5=25}
// 更复杂的转换
data class Student(val id: Int, val name: String, val grade: String)
val students = listOf(
Student(1, "Alice", "A"),
Student(2, "Bob", "B"),
Student(3, "Charlie", "A")
)
val studentMap = students.associate { student ->
student.name to mapOf(
"id" to student.id,
"grade" to student.grade
)
}
/*
结果: {
"Alice"={"id"=1, "grade"="A"},
"Bob"={"id"=2, "grade"="B"},
"Charlie"={"id"=3, "grade"="A"}
}
*/
5. groupBy - 分组转换
data class Person(val name: String, val age: Int, val city: String)
val people = listOf(
Person("Alice", 25, "New York"),
Person("Bob", 30, "London"),
Person("Charlie", 25, "New York"),
Person("David", 30, "Paris"),
Person("Eve", 25, "London")
)
// 按年龄分组
val byAge = people.groupBy { it.age }
/*
结果: Map<Int, List<Person>>
{
25=[Person(name=Alice, age=25, city=New York),
Person(name=Charlie, age=25, city=New York),
Person(name=Eve, age=25, city=London)],
30=[Person(name=Bob, age=30, city=London),
Person(name=David, age=30, city=Paris)]
}
*/
// 按城市分组,并只提取名字
val namesByCity = people.groupBy(
keySelector = { it.city },
valueTransform = { it.name }
)
// 结果: {"New York"=["Alice", "Charlie"], "London"=["Bob", "Eve"], "Paris"=["David"]}
// 多层分组
val multiGroup = people.groupBy(
keySelector = { it.city },
valueTransform = { it.age }
).mapValues { (_, ages) ->
ages.groupBy { it }
}
/*
结果: {
"New York"={25=[25, 25]},
"London"={30=[30], 25=[25]},
"Paris"={30=[30]}
}
*/
6. zip 系列 - 合并转换
zip - 两个集合合并
val names = listOf("Alice", "Bob", "Charlie")
val ages = listOf(25, 30, 35)
// 基本合并
val zipped = names.zip(ages) // [("Alice", 25), ("Bob", 30), ("Charlie", 35)]
// 自定义合并函数
val people = names.zip(ages) { name, age ->
Person(name, age, "Unknown")
}
// 结果: [Person(Alice,25,Unknown), Person(Bob,30,Unknown), Person(Charlie,35,Unknown)]
// 不等长合并
val list1 = listOf(1, 2, 3)
val list2 = listOf("A", "B", "C", "D", "E")
val result = list1.zip(list2) // 结果长度取较小值: [(1, "A"), (2, "B"), (3, "C")]
zipWithNext - 相邻元素合并
val numbers = listOf(1, 2, 3, 4, 5)
// 相邻元素配对
val pairs = numbers.zipWithNext() // [(1, 2), (2, 3), (3, 4), (4, 5)]
// 计算相邻元素的差值
val differences = numbers.zipWithNext { a, b -> b - a }
// 结果: [1, 1, 1, 1]
// 创建滑动窗口
fun <T> List<T>.window(size: Int): List<List<T>> {
return this.zipWithNext().windowed(size) { window ->
listOf(window.first, window.second)
}
}
7. windowed - 滑动窗口转换
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8)
// 创建大小为3的滑动窗口
val windows = numbers.windowed(3)
// 结果: [[1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5, 6], [5, 6, 7], [6, 7, 8]]
// 计算滑动平均值
val movingAverage = numbers.windowed(3) { window ->
window.average()
}
// 结果: [2.0, 3.0, 4.0, 5.0, 6.0, 7.0]
// 配置窗口参数
val partialWindows = numbers.windowed(
size = 3,
step = 2, // 步长为2
partialWindows = true // 包含不完整的窗口
)
// 结果: [[1, 2, 3], [3, 4, 5], [5, 6, 7], [7, 8]]
// 查找递增序列
val increasing = numbers.windowed(2) { (a, b) ->
b > a
}
// 结果: [true, true, true, true, true, true, true]
8. chunked - 分块转换
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// 分成大小为3的块
val chunks = numbers.chunked(3)
// 结果: [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]
// 对每个块进行转换
val chunkSums = numbers.chunked(3) { chunk ->
chunk.sum()
}
// 结果: [6, 15, 24, 10]
// 数据库分页查询模拟
data class User(val id: Int, val name: String)
fun fetchUsersInPages(pageSize: Int): List<List<User>> {
val allUsers = (1..100).map { User(it, "User$it") }
return allUsers.chunked(pageSize)
}
// 分批处理大数据集
fun processLargeDataset(data: List<Int>, batchSize: Int) {
data.chunked(batchSize).forEachIndexed { index, batch ->
println("处理批次 $index: $batch")
// 处理每个批次
}
}
9. 性能优化和注意事项
使用 Sequence 进行惰性计算
// List.map 会立即计算所有结果
val immediate = (1..1_000_000).map { it * 2 } // 立即创建百万元素的列表
// Sequence.map 是惰性的
val lazy = (1..1_000_000).asSequence()
.map { it * 2 }
.take(10) // 只计算前10个元素
.toList()
// 链式操作使用 Sequence 更高效
val result = (1..1_000_000).asSequence()
.filter { it % 2 == 0 } // 惰性过滤
.map { it * 3 } // 惰性映射
.take(100) // 只取100个
.toList()
避免不必要的中间集合
// 不好:创建多个中间集合
val bad = listOf(1, 2, 3, 4, 5)
.filter { it % 2 == 0 } // 中间集合 [2, 4]
.map { it * 2 } // 中间集合 [4, 8]
.sum() // 12
// 好:使用 asSequence() 避免中间集合
val good = listOf(1, 2, 3, 4, 5).asSequence()
.filter { it % 2 == 0 }
.map { it * 2 }
.sum()
// 更好:使用 sumOf(如果有的话)
val best = listOf(1, 2, 3, 4, 5)
.sumOf { if (it % 2 == 0) it * 2 else 0 }
处理空集合
val emptyList = emptyList<Int>()
// map 处理空集合是安全的
val result1 = emptyList.map { it * 2 } // 返回空列表
// 提供默认值
val numbers = emptyList<Int>()
val defaultMapped = numbers.ifEmpty { listOf(0) }.map { it * 2 }
// 结果: [0]
// 处理可能为空的转换
fun safeTransform(list: List<Int>?): List<String> {
return list?.map { it.toString() } ?: emptyList()
}
10. 实际应用示例
数据处理管道
data class Sale(val product: String, val amount: Double, val date: String)
val sales = listOf(
Sale("Laptop", 999.99, "2023-01-15"),
Sale("Phone", 699.99, "2023-01-15"),
Sale("Tablet", 399.99, "2023-01-16"),
Sale("Laptop", 999.99, "2023-01-16"),
Sale("Phone", 699.99, "2023-01-17")
)
// 复杂的转换管道
val salesReport = sales
.groupBy { it.date } // 按日期分组
.mapValues { (date, dailySales) ->
mapOf(
"total" to dailySales.sumOf { it.amount },
"count" to dailySales.size,
"products" to dailySales
.groupBy { it.product }
.mapValues { (_, productSales) ->
productSales.sumOf { it.amount }
},
"topProduct" to dailySales
.groupBy { it.product }
.maxByOrNull { (_, productSales) ->
productSales.sumOf { it.amount }
}?.key
)
}
println(salesReport)
API 响应转换
// 模拟 API 响应
data class ApiResponse(
val success: Boolean,
val data: List<ApiUser>?,
val message: String?
)
data class ApiUser(
val userId: Int,
val userName: String,
val userEmail: String,
val roles: List<String>?
)
data class DomainUser(
val id: Int,
val name: String,
val email: String,
val isAdmin: Boolean
)
fun transformApiResponse(response: ApiResponse): List<DomainUser> {
return response.data?.mapNotNull { apiUser ->
// 安全检查
if (apiUser.userName.isBlank() || apiUser.userEmail.isBlank()) {
return@mapNotNull null
}
// 转换为领域模型
DomainUser(
id = apiUser.userId,
name = apiUser.userName,
email = apiUser.userEmail,
isAdmin = apiUser.roles?.contains("ADMIN") ?: false
)
} ?: emptyList()
}
配置处理
// 从配置文件读取并转换
data class ServerConfig(
val name: String,
val host: String,
val port: Int,
val ssl: Boolean
)
fun parseConfig(configLines: List<String>): List<ServerConfig> {
return configLines
.map { it.trim() }
.filter { it.isNotBlank() && !it.startsWith("#") }
.map { line ->
val parts = line.split("=", limit = 2)
parts[0].trim() to parts.getOrNull(1)?.trim() ?: ""
}
.groupBy { (key, _) ->
key.substringBefore(".")
}
.map { (serverName, properties) ->
val props = properties.associate { (key, value) ->
key.substringAfter(".") to value
}
ServerConfig(
name = serverName,
host = props["host"] ?: "localhost",
port = props["port"]?.toIntOrNull() ?: 8080,
ssl = props["ssl"]?.toBoolean() ?: false
)
}
}
11. 自定义扩展的 map 函数
// 为特定类型创建专用 map 函数
fun List<String>.mapToInt(): List<Int> {
return this.mapNotNull { it.toIntOrNull() }
}
// 带默认值的 map
inline fun <T, R> Iterable<T>.mapOrDefault(
transform: (T) -> R,
default: R
): List<R> {
return this.map { transform(it) ?: default }
}
// 并行映射(简单示例)
fun <T, R> List<T>.parallelMap(
parallelism: Int = Runtime.getRuntime().availableProcessors(),
transform: (T) -> R
): List<R> {
return this.chunked(maxOf(1, size / parallelism))
.parallelStream()
.flatMap { chunk ->
chunk.map(transform).stream()
}
.toList()
}
// 深度映射嵌套集合
fun <T, R> List<List<T>>.deepMap(transform: (T) -> R): List<List<R>> {
return this.map { innerList ->
innerList.map(transform)
}
}
总结
map 函数及其变体是 Kotlin 集合处理的核心工具:
map- 基本变换,一对一转mapIndexed- 带索引的变换mapNotNull- 过滤空值的变换flatMap- 展平嵌套结构的变换associate系列 - 转换为 MapgroupBy- 分组转换zip系列 - 合并转换windowed- 滑动窗口转换chunked- 分块转换
性能提示:
- 对大数据集使用
Sequence进行惰性计算 - 避免不必要的中间集合
- 链式操作时考虑使用
asSequence() - 选择合适的变换函数避免重复操作
map 函数是函数式编程的基石,掌握这些变换函数能让你写出更简洁、高效的 Kotlin 代码。