因为list可能在遍历中被修改,每次调用size()可确保按当前真实长度访问,避免索引越界或跳过元素;缓存size值会导致语义不安全。

用 for 循环遍历 List 时,为什么 list.size() 要在每次迭代中重新调用?
因为 List 实例可能在遍历过程中被其他线程或代码逻辑修改(比如删除元素),导致索引越界或跳过元素。虽然多数场景下 size() 是 O(1),但关键不是性能,而是语义安全——它保证你始终按当前真实长度访问。
常见错误是提前缓存 int len = list.size(),然后用 i 判断,这在并发或中间有 <code>remove() 时会出错。
- 只在确定集合不会被修改时才缓存
size() - 若需边遍历边删除,改用
Iterator.remove(),而不是list.remove(i) - 对
LinkedList,用get(i)是 O(n) 复杂度,此时 for 循环效率远低于迭代器
增强 for 循环(for-each)底层到底调用了什么?
编译后等价于显式使用 Iterator:调用 list.iterator() 获取迭代器,再反复调用 hasNext() 和 next()。这意味着它天然支持所有实现了 Iterable 接口的集合,但不暴露迭代器本身。
陷阱在于:无法在循环体内调用 list.remove(),否则抛 ConcurrentModificationException;想删除必须用 Iterator.remove(),而增强 for 不提供该引用。
立即学习“Java免费学习笔记(深入)”;
- 适合只读遍历,代码简洁可读性高
- 对
ArrayList性能接近传统 for;对LinkedList更优(避免了随机访问开销) - 不能获取当前索引,也不能在循环中修改集合结构
Iterator 遍历时调用 remove() 为什么比 list.remove(Object) 安全?
因为 Iterator.remove() 是唯一被允许在迭代过程中修改集合的方式,它会同步更新内部 modCount 和 expectedModCount,从而绕过快速失败(fail-fast)检查。
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
if (s.startsWith("A")) {
it.remove(); // ✅ 合法且高效
}
}
-
list.remove(s)会触发modCount变更,但迭代器不知情,下次next()就抛异常 -
Iterator.remove()只能删除上一次next()返回的元素,不能连续调两次 - 对
CopyOnWriteArrayList,Iterator.remove()是空操作(不支持),此时只能用普通remove()
Lambda 的 forEach() 和 Stream 的 forEach() 有什么本质区别?
前者是 Iterable.forEach() 默认方法,直接委托给 Iterator;后者是 Stream 的终端操作,底层可能并行(取决于是否调用 parallelStream()),且有短路、延迟执行等特性。
最易忽略的一点:Stream 的 forEach() **不保证顺序**(即使在顺序流中,JVM 也不强制规定执行顺序),而 Iterable.forEach() 严格按集合顺序执行。
- 多线程环境下,
list.forEach(System.out::println)是线程安全的(只要 list 本身不被并发修改) -
list.stream().forEach(...)在并行流中可能乱序,需用forEachOrdered()强制顺序 - Stream 版本无法在 lambda 中抛受检异常,必须包装成
RuntimeException










