错误在于循环条件应为 i < list.size(),而非 i <= list.size(),否则当 i 等于 size 时访问 list.get(i) 将触发 IndexOutOfBoundsException。

用 for 循环配合 size() 遍历 List 时,下标越界是高频错误
直接写 for (int i = 0; i 会导致 <code>IndexOutOfBoundsException —— 因为合法下标范围是 [0, size() - 1],而 多跑了一次。Java 的 <code>List 是零起点索引,这点和 Python 不同,别被惯性带偏。
正确写法必须是:i 。常见业务中还要注意:如果循环里会删元素,<code>size() 动态变化,但 i 还在递增,极易跳过相邻项。
- 只读遍历:用
for (int i = 0; i 安全 - 边遍历边删除:改用
Iterator.remove()或倒序for (int i = list.size() - 1; i >= 0; i--) - 需要索引 + 元素值:优先考虑增强
for(for (Type item : list))+ 单独计数器,更简洁不易错
size() 返回的是当前长度,不是容量,且不保证线程安全
ArrayList.size() 是 O(1) 操作,它只是返回内部 elementData 数组中实际存了多少个元素;LinkedList.size() 在 Java 8+ 也是 O(1),因为维护了 size 字段。但要注意:这个值不反映底层数组容量(ArrayList 的 capacity),也不能用于判断是否“满”——size() == capacity 才会触发扩容。
多线程环境下,如果多个线程同时修改 List,size() 可能返回过期值。比如 A 线程刚调完 add(),B 线程立刻读 size(),可能还是旧值(除非用 Collections.synchronizedList() 或 CopyOnWriteArrayList)。
- 单线程场景:放心用
size()控制循环边界 - 并发修改场景:避免依赖
size()做条件判断,改用isEmpty()或同步块包裹 - 性能敏感循环:不要在每次迭代里重复调
list.size(),提前缓存:int len = list.size(); for (int i = 0; i
增强 for 和传统 for + size() 的适用边界很明确
增强 for(for (E e : list))本质是语法糖,编译后仍用 Iterator。它不能直接拿到索引,也不支持在循环中调 list.remove(i)(会抛 ConcurrentModificationException)。所以当业务需要索引位置(比如日志打桩标序号、批量更新第 N 条)、或需根据索引做分支逻辑(如奇偶处理、首尾特殊逻辑),就必须用传统 for + size()。
- 只要取元素值、不做索引相关操作 → 用增强
for,代码干净、不易越界 - 要改元素(如
list.set(i, newValue))、要索引参与计算、要按位置分批 → 用for (int i = 0; i - 要删元素且依赖索引条件(如“删掉所有索引为偶数的字符串”)→ 倒序循环,避免下标偏移
遍历中修改 List 导致的 ConcurrentModificationException 不是“并发”问题
这个异常名有误导性:即使单线程,只要用增强 for 或 Iterator 遍历时,直接调 list.remove() 或 list.add(),就会触发它。因为 ArrayList 内部有 modCount 计数器,Iterator 创建时记下初值,每次调 next() 都校验是否被外部修改过。
解决方法不是加锁,而是选对 API:
- 删元素:用
iterator.remove()(安全,且只能删当前项) - 批量删满足条件的项:用
removeIf(predicate)(Java 8+,内部已处理modCount) - 必须用索引删:用倒序
for,或收集待删索引再统一删(list.removeAll(toRemove))
真正容易被忽略的是:有些工具类方法(如 list.subList().clear())也会触发这个异常,因为它底层仍是结构修改。遇到 ConcurrentModificationException,先看是不是在遍历中动了原 List 的结构,而不是急着上 synchronized。

















