1. 概述

在本教程中,我们将学习如何在 Kotlin 中将一个对象列表转换为字符串列表。这种场景在实际开发中非常常见,尤其是在需要从复杂对象中提取某些字段组成简单列表时。

我们将主要介绍 Kotlin 集合的 map() 函数及其变体,以及 for 循环和 flatMap() 的使用方式。

2. 问题描述:将对象列表转换为字符串列表

为了更好地说明问题,我们先定义一个 Employee 数据类,并创建一个 Employee 对象列表。我们的目标是提取这些员工的名字组成一个字符串列表。

2.1 定义 Employee

data class Employee (
    val empId: Int,
    val firstName: String,
    val lastName: String,
    val monthlySalary: Double
) {
    fun name(): String {
        return "$firstName $lastName"
    }
}

2.2 创建员工列表

private val empList = listOf(
    Employee(1, "John", "Doe", Random.nextDouble(1000.0, 5000.0)),
    Employee(2, "Jill", "Fowler", Random.nextDouble(1000.0, 5000.0)),
    Employee(3, "Jack", "Smith", Random.nextDouble(1000.0, 5000.0)),
    Employee(4, "John", "Doe", Random.nextDouble(1000.0, 5000.0))
)

2.3 预期结果

private val expectedEmpNames = listOf("John Doe", "Jill Fowler", "Jack Smith", "John Doe")

接下来我们将使用多种方式来实现 empListexpectedEmpNames 的转换。


3. 使用 map() 函数转换列表

map() 是 Kotlin 集合中最常用的转换函数之一。它接收一个 lambda 表达式,用于将每个元素映射为新的值。

3.1 基本使用 map()

val empNameList = empList.map { it.name() }
assertTrue(empNameList.isNotEmpty())
assertTrue(empNameList == expectedEmpNames)

✅ 优点:简洁、语义清晰
❌ 缺点:不处理 null 值(如果元素可能为 null,需使用 mapNotNull()


3.2 使用 mapNotNull() 过滤 null 值

当列表中包含 null 时,可以使用 mapNotNull() 自动过滤掉 null 元素:

val empListWithNulls = listOf(*empList.toTypedArray(), null)
val empNameList = empListWithNulls.mapNotNull { it?.name() }
assertTrue(empNameList.isNotEmpty())
assertTrue(empNameList == expectedEmpNames)

⚠️ 注意:使用 it?.name() 是安全调用,防止 null 引发异常。


3.3 使用 mapTo() 转换到已有列表

如果你希望将结果收集到一个已有的可变列表中,可以使用 mapTo()

val empNameList = mutableListOf<String>()
empList.mapTo(empNameList) { it.name() }
assertTrue(empNameList.isNotEmpty())
assertTrue(empNameList == expectedEmpNames)

3.4 使用 mapIndexed() 获取索引信息

如果你需要在转换过程中访问元素的索引,可以使用 mapIndexed()

val empNameList = empList.mapIndexed { index, item ->
    item.name()
}
assertTrue(empNameList.isNotEmpty())
assertTrue(empNameList == expectedEmpNames)

⚠️ 虽然索引在本例中没有使用,但当你需要根据位置进行逻辑处理时非常有用。


4. 使用 for 循环转换列表

虽然 Kotlin 提供了函数式 API,但传统的 for 循环在某些场景下依然适用,尤其是对可读性要求较高的场景。

4.1 普通 for 循环

val empNameList = mutableListOf<String>()
for (emp in empList) {
    empNameList.add(emp.name())
}
assertTrue(empNameList.isNotEmpty())
assertTrue(empNameList == expectedEmpNames)

4.2 forEach() 循环

val empNameList = mutableListOf<String>()
empList.forEach { emp ->
    empNameList.add(emp.name())
}
assertTrue(empNameList.isNotEmpty())
assertTrue(empNameList == expectedEmpNames)

✅ 优点:直观、易于调试
❌ 缺点:代码冗长,不如 map() 简洁


5. 使用 flatMap() 转换嵌套对象列表

当面对嵌套结构时,比如一个组织包含多个员工,我们可以通过 flatMap() 将嵌套结构“展平”并提取字段。

5.1 定义 Organization

data class Organization (
    val orgId: Int,
    val employees: List<Employee>
)

5.2 创建组织列表

val empByOrgList = listOf(
    Organization(
        100,
        listOf(
            Employee(1, "John", "Doe", 1200.0),
            Employee(2, "Jill", "Fowler", 1200.0)
        )
    ),
    Organization(
        200,
        listOf(
            Employee(3, "Jack", "Smith", 1200.0),
            Employee(4, "John", "Doe", 1200.0)
        )
    )
)

5.3 使用 flatMap() 提取所有员工名字

val empNameList = empByOrgList
    .flatMap { it.employees }
    .map { it.name() }
assertTrue(empNameList.isNotEmpty())
assertTrue(empNameList == expectedEmpNames)

✅ 优点:适用于嵌套结构,逻辑清晰
⚠️ 注意:flatMap() 会将多个列表合并为一个,避免使用 map().flatten() 等价写法。


6. 总结

在本教程中,我们学习了多种方法将一个自定义对象列表转换为字符串列表:

方法 是否推荐 说明
map() ✅ 推荐 最常用,语法简洁
mapNotNull() ✅ 推荐 处理可能为 null 的元素
mapTo() ✅ 可选 转换到已有集合
mapIndexed() ✅ 可选 需要索引时使用
for / forEach ⚠️ 一般 可读性好,但不够函数式
flatMap() ✅ 推荐 处理嵌套结构时非常有效

最终选择哪种方式取决于具体业务场景和团队编码风格。函数式写法通常更简洁,但也要注意可维护性。

完整的示例代码可在 GitHub 上找到。


原始标题:Converting a Custom Object List to Simple List in Kotlin