Kotlin中forEach的索引:如何不丢失位置并加速代码

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

如何在Kotlin的forEach中获取当前索引:从新手到专家

\n\n

想象一下:你用Kotlin编写代码,处理一个用户列表,突然发现你不仅需要元素本身,还需要它在集合中的位置。你下意识地想要使用老式的 for (i in 0 until list.size)…… 打住!Kotlin有更优雅且高性能的方式。在本文中,我们将探讨如何在 forEach Kotlin中获取当前索引,比较不同方法的性能,并为你的项目选择最佳方案。这不仅仅是文档的翻译——这是基于实际基准测试和Stack Overflow社区经验的成果。

\n\n

为什么没有索引的forEach是个问题?

\n\n

标准的 list.forEach { item -> ... } 很方便,但当需要知道元素位置时,许多人会写出临时解决方案:

\n\n
var index = 0\nlist.forEach { item ->\n    println("$index: $item")\n    index++\n}
\n\n

这虽然可行,但显得冗长且容易出错(忘记递增?就会产生bug)。此外,循环外部的可变变量违反了函数式编程风格,在多线程场景下可能成为问题。让我们看看正确的解决方案。

\n\n

方法1:使用 withIndex() —— 简单而优雅

\n\n

Stack Overflow上最流行的答案是 withIndex() 函数。它返回一个 IndexedValue 对的序列,其中每个元素都有 index 和 value 字段。

\n\n
val list = listOf("Kotlin", "Java", "Python")\nlist.withIndex().forEach { (index, value) ->\n    println("元素 #$index: $value")\n}
\n\n

这里发生了什么?withIndex() 创建了一个惰性序列(lazy),将每个元素包装在 IndexedValue 对象中。解构声明 (index, value) 使代码简洁且可读。

\n\n

优点:

\n
    \n
  • 简洁——一行代码代替三行
  • \n
  • 惰性——不会在内存中创建多余的集合
  • \n
  • 可读性——一眼就能看出我们在处理索引
  • \n
\n\n

缺点:

\n
    \n
  • 创建 IndexedValue 包装器有轻微开销(不过在99%的情况下可以忽略不计)
  • \n
\n\n

方法2:forEachIndexed —— 内置的替代方案

\n\n

Kotlin提供了现成的 forEachIndexed 函数,它接受一个带有两个参数的lambda:索引和值。

\n\n
list.forEachIndexed { index, value ->\n    println("位置 $index: $value")\n}
\n\n

这与 withIndex().forEach 几乎相同,但少了一次函数调用。在内部实现中,forEachIndexed 是否也使用了 IndexedValue?不,它直接传递索引,因此速度稍快。

\n\n

优点:

\n
    \n
  • 最简短的写法
  • \n
  • 没有创建中间对象
  • \n
  • 符合Kotlin的惯用风格
  • \n
\n\n

缺点:

\n
    \n
  • 不如 withIndex() 灵活——无法与其他链式操作符组合(例如在 forEach 之前使用 filter)
  • \n
\n\n

方法3:filterIndexed —— 当需要按索引过滤时

\n\n

如果你不仅需要获取索引,还需要根据元素位置进行过滤,filterIndexed 是你的选择。

\n\n
val list = listOf("a", "b", "c", "d", "e")\nval evenIndexed = list.filterIndexed { index, _ -> index % 2 == 0 }\nprintln(evenIndexed) // [a, c, e]
\n\n

注意:第二个参数是元素本身,但我们使用 _ 忽略它。这是Kotlin中处理未使用变量的标准做法。

\n\n

优点:

\n
    \n
  • 灵活性——可以组合过滤和转换操作
  • \n
  • 纯函数式风格
  • \n
\n\n

缺

博客

书籍推荐