1. 简介
在遍历一个 List
的时候,有时我们不仅需要当前元素,还需要访问前一个元素。比如,我们有一个列表 [1, 2, 3, 4, 5]
,我们希望依次处理:只看 1,然后看 1 和 2,接着是 2 和 3,以此类推。最终可能想生成一个新列表,包含所有相邻元素的组合,如:["1", "12", "23", "34", "45"]
。
在本教程中,我们将介绍几种 Kotlin 中实现这种需求的方式,包括使用传统循环、foldIndexed()
、zipWithNext()
和 scan()
方法等。这些方法都适用于需要在遍历中引用前一个元素的场景。
2. 使用 for 循环
这是最直接的方法,通过索引访问前一个元素:
fun iterateListUsingLoop(list: List<Int>): List<String> {
val newList = mutableListOf<String>()
newList.add(list[0].toString())
for (i in 1 until list.size) {
newList.add("${list[i - 1]}${list[i]}")
}
return newList
}
✅ 说明:
- 创建一个
MutableList
存储结果; - 第一个元素单独处理;
- 从索引 1 开始遍历,每次访问当前元素和前一个元素;
- 将它们拼接后加入新列表。
✅ 测试用例:
@Test
fun `creates new list by adding elements from original list using loop`() {
val list = listOf(1, 2, 3, 4, 5)
val expectedList = listOf("1", "12", "23", "34", "45")
assertEquals(expectedList, iterateListUsingLoop(list))
}
✅ 优点: 逻辑清晰,适合初学者理解和使用。
⚠️ 缺点: 需要手动处理索引边界。
3. 使用 foldIndexed 方法
foldIndexed()
是 Kotlin 标准库中的高阶函数,适合在遍历过程中累积结果,同时可以访问索引:
fun iterateListUsingFoldIndexed(list: List<Int>): List<String> {
return list.foldIndexed(mutableListOf()) { i, acc, element ->
if(i == 0) {
acc.add(element.toString())
} else {
acc.add("${list[i - 1]}$element")
}
acc
}
}
✅ 说明:
- 使用
foldIndexed
构建一个累加器(accumulator); - 索引为 0 时,仅添加当前元素;
- 索引大于 0 时,拼接当前元素与前一个元素;
- 最终返回整个结果列表。
✅ 测试用例:
@Test
fun `creates new list by adding elements from original list using fold method`() {
val list = listOf(1, 2, 3, 4, 5)
val expectedList = listOf("1", "12", "23", "34", "45")
assertEquals(expectedList, iterateListUsingFoldIndexed(list))
}
✅ 优点: 函数式风格,代码简洁。
⚠️ 缺点: 对不熟悉高阶函数的开发者来说可读性略差。
4. 使用 zipWithNext 方法
zipWithNext()
是 Kotlin 标准库中用于将列表中相邻元素配对的方法,非常适合此类场景:
@Test
fun `creates new list by adding elements from original list using zipWithNext method`() {
val list = listOf("1", "2", "3", "4", "5")
val expectedList = listOf("1", "12", "23", "34", "45")
val result = (list.take(1) + list.zipWithNext { a, b -> "$a$b" })
assertEquals(expectedList, result)
}
✅ 说明:
take(1)
用于保留第一个元素;zipWithNext
会生成(a, b)
对,其中a
是当前元素,b
是下一个元素;- 然后将这两个元素拼接起来;
- 最终将第一个元素与拼接结果合并成一个完整列表。
✅ 优点: 非常直观,代码简短。
⚠️ 缺点: 不适合需要访问前一个元素的复杂逻辑。
5. 使用 scan 方法
scan
是 Kotlin 1.4+ 引入的新方法,非常适合需要逐步累积状态的场景:
@Test
fun `creates new list by adding elements from original list using scan method`() {
val list = listOf(1, 2, 3, 4, 5)
val expectedList = listOf("1", "12", "23", "34", "45")
val result = list.drop(1).scan(list.first().toString()) { acc, i -> acc.takeLast(1) + i.toString() }
assertEquals(expectedList, result)
}
✅ 说明:
drop(1)
用于跳过第一个元素;- 初始值为第一个元素的字符串;
- 每次取前一次结果的最后一个字符,与当前元素拼接;
- 逐步构建出最终的字符串列表。
✅ 优点: 适合需要累积状态的复杂逻辑。
⚠️ 缺点: 理解门槛稍高,不适合新手。
6. 总结
方法 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
for 循环 |
通用、直观 | 简单易懂 | 需要手动处理索引边界 |
foldIndexed() |
函数式编程风格 | 代码简洁 | 对新手不太友好 |
zipWithNext() |
遍历相邻元素 | 非常简洁 | 无法处理复杂逻辑 |
scan() |
状态累积型逻辑 | 灵活强大 | 理解成本较高 |
✅ 建议:
- 如果逻辑简单,优先使用
for
或zipWithNext()
; - 如果需要函数式风格,使用
foldIndexed()
; - 如果需要维护状态或处理更复杂的累积逻辑,使用
scan()
。
在实际开发中,选择哪种方式取决于项目风格和团队熟悉度。合理使用这些技巧,可以写出更简洁、更易维护的 Kotlin 代码。