2-2-27 快速掌握Kotlin-变换函数 map

38 阅读8分钟

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]

flatMapmap 的区别

// 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 集合处理的核心工具:

  1. map - 基本变换,一对一转
  2. mapIndexed - 带索引的变换
  3. mapNotNull - 过滤空值的变换
  4. flatMap - 展平嵌套结构的变换
  5. associate 系列 - 转换为 Map
  6. groupBy - 分组转换
  7. zip 系列 - 合并转换
  8. windowed - 滑动窗口转换
  9. chunked - 分块转换

性能提示:

  • 对大数据集使用 Sequence 进行惰性计算
  • 避免不必要的中间集合
  • 链式操作时考虑使用 asSequence()
  • 选择合适的变换函数避免重复操作

map 函数是函数式编程的基石,掌握这些变换函数能让你写出更简洁、高效的 Kotlin 代码。