Kotlin使用for循环(indices)为何会引发索引异常?forEachIndexed与该循环的区别及优选方案咨询
嘿,这个问题我之前也碰到过,给你好好捋捋~
为什么两种遍历方式会有差异?
咱们先拆解两种写法的底层逻辑:
1. for (i in list.indices) 偶尔抛异常的原因
当你写 for (i in list.indices) 时,list.indices 是在循环开始前就计算好的,会得到一个固定的索引范围(比如列表初始有3个元素,那就是 0..2)。
如果在循环执行的过程中——不管是当前线程的其他逻辑,还是其他线程——修改了这个可变列表(比如调用 clear() 把列表清空,或者删除元素导致size变成0),那当循环到某个索引 i 时,再去执行 list[i] 就会因为此时列表为空(length=0),直接抛出 ArrayIndexOutOfBoundsException。
举个具体场景:假设列表一开始有1个元素,indices 得到 0..0,进入循环后,在执行 val item = list[i] 之前,列表被清空了,这时候访问索引0自然就报错了。
2. forEachIndexed 运行正常的原因
Kotlin 里的 forEachIndexed 是基于迭代器遍历实现的(看源码的话,它内部会遍历列表的迭代器,逐个取出元素并传递索引和元素给lambda)。
这种遍历方式不会预先计算整个索引范围,而是跟着迭代器一步步走:每拿到一个元素,才会执行对应的逻辑。如果在遍历过程中列表被修改,迭代器会直接终止遍历(或者根据集合类型触发fail-fast机制,但你这里没报错,说明修改发生在遍历完成后,或者迭代器已经没有元素可遍历了),不会出现访问不存在索引的情况。
Kotlin开发中更推荐哪种遍历方式?
在大多数场景下,优先推荐使用 forEach、forEachIndexed 这类函数式遍历API,原因有这几点:
- 代码更简洁,可读性更高,不需要手动管理索引,减少出错概率;
- 天然避免了手动索引访问带来的越界问题,尤其是列表可能被动态修改的场景;
- 能更好地和Kotlin的其他特性结合(比如链式调用、lambda简化写法)。
如果确实需要手动操作索引(比如要修改列表元素、有复杂的索引逻辑),可以用 for ((index, item) in list.withIndex()),它和 forEachIndexed 逻辑类似,也是基于迭代器的,不会预先锁定索引范围,比直接用 list.indices 更安全。
当然,如果你的列表会被多线程操作,那不管用哪种遍历方式,都要注意线程安全——要么用线程安全的集合(比如 CopyOnWriteArrayList),要么加同步锁保护列表的读写操作。
内容的提问来源于stack exchange,提问作者fan Hsu




