Java集合遍历方式比较:For循环与迭代器
本文对比了for循环与Iterator。核心区别在于:for-each最简洁,适合只读;Iterator是遍历时安全删除元素的唯一方式;传统for循环则允许你通过索引进行完全控制。
这是一个非常经典且重要的问题。for循环和Iterator(迭代器)都是用来遍历集合(如List, Set, Map)中元素的工具,但它们在原理、使用场景和功能上有显著的区别。
为了说清楚,我们把“for循环”分为两种:
- 传统
for循环 (使用索引) - 增强
for循环 (for-each)
下面我们来详细对比这三者(传统for、for-each、Iterator)。
核心概念与比喻
- 传统
for循环:就像你有一排带编号的储物柜(比如数组或ArrayList),你拿着一个清单,上面写着“检查1号柜,检查2号柜,...”。你完全控制着要检查哪个柜子(索引)。 - Iterator (迭代器):就像你在看一本没有页码的书,但你有一个书签。你只能做三件事:问“后面还有内容吗?”(
hasNext()),翻到下一页并阅读内容(next()),或者撕掉当前这一页(remove())。你不能跳着看,只能顺序地向后移动书签。 - 增强
for循环 (for-each):它是Iterator的一个“语法糖”(Syntactic Sugar)。它帮你隐藏了hasNext()和next()的繁琐细节,让你能用最简洁的方式“从头到尾读完整本书”。
详细区别对比
| 特性/方面 | 传统 for 循环 (for-i) |
增强 for 循环 (for-each) |
Iterator (迭代器) |
|---|---|---|---|
| 底层原理 | 基于索引(index)访问。通过get(i)方法获取元素。 |
本质是Iterator。编译器会自动将其转换为Iterator的调用方式。 | 是一个接口,提供统一的遍历方式,隐藏了集合的内部实现。 |
| 适用范围 | 仅适用于有序、可通过索引访问的集合,如Array、ArrayList。不适用于Set、Map(虽然可以转成List再用)。 |
适用于所有实现了Iterable接口的类(几乎所有Java集合类)。 |
适用于所有实现了Iterable接口的类,通过调用iterator()方法获得。 |
| 遍历时修改 | 极其危险。如果在遍历时增删元素,会导致索引错乱或IndexOutOfBoundsException。 |
绝对禁止。如果在遍历时通过集合自身的方法(如list.remove())增删元素,会立即抛出ConcurrentModificationException异常。 |
唯一安全的方式。必须使用iterator.remove()方法来删除当前元素,这是专门为此设计的。 |
| 语法简洁度 | 较繁琐,需要初始化、判断条件和更新循环变量。for(int i=0; i<list.size(); i++) |
最简洁,可读性最好。for(String s : list) |
相对繁琐,需要手动调用hasNext()和next()。while(it.hasNext()){ it.next(); } |
| 控制力 | 最强。可以获取当前索引i,可以从后向前遍历,可以跳跃遍历(i+=2)。 |
最弱。只能从头到尾顺序遍历,无法获取当前元素的索引。 | 只能从头到尾顺序遍历(ListIterator除外),无法获取索引。 |
| 性能 | - 对ArrayList:非常高,因为是随机访问(O(1))。- 对 LinkedList:极低,因为每次get(i)都要从头查找(O(n)),总时间复杂度为O(n²)。 |
- 对ArrayList:性能略低于传统for循环(因为有Iterator对象创建和方法调用的开销),但差别很小。- 对 LinkedList:性能高,因为Iterator内部维护了指向下一个节点的指针(O(n))。 |
与for-each性能基本一致,因为for-each就是基于它实现的。 |
代码示例
假设我们有一个ArrayList,需要删除其中所有 "B"。
java
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class LoopComparison {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
list.add("B");
// 1. 传统 for 循环 (错误示范 - 会漏掉元素)
/*
for (int i = 0; i < list.size(); i++) {
if ("B".equals(list.get(i))) {
list.remove(i); // 错误!删除元素后,后续元素的索引会前移
// 导致紧邻的下一个"B"被跳过
}
}
System.out.println("传统for循环后: " + list); // 输出: [A, C, B] - 删错了
*/
// 2. 增强 for 循环 (错误示范 - 抛出异常)
/*
try {
for (String s : list) {
if ("B".equals(s)) {
list.remove(s); // 错误!会抛出 ConcurrentModificationException
}
}
} catch (Exception e) {
System.out.println("增强for循环出错: " + e);
}
*/
// 3. Iterator (正确且推荐的方式)
List<String> listForIterator = new ArrayList<>();
listForIterator.add("A");
listForIterator.add("B");
listForIterator.add("C");
listForIterator.add("B");
Iterator<String> iterator = listForIterator.iterator();
while (iterator.hasNext()) {
String current = iterator.next();
if ("B".equals(current)) {
iterator.remove(); // 正确!使用迭代器自身的remove方法
}
}
System.out.println("Iterator处理后: " + listForIterator); // 输出: [A, C] - 完全正确
}
}
总结与最佳实践
只读遍历:
- 首选增强
for循环 (for-each)。它最简洁、可读性最好,且不易出错。 - 例如:
for (User user : userList) { System.out.println(user.getName()); }
- 首选增强
需要删除元素:
- 必须使用
Iterator。这是唯一安全、可靠地在遍历时修改集合的方式。 - 例如:
while (iterator.hasNext()) { if (shouldRemove(iterator.next())) { iterator.remove(); } }
- 必须使用
需要访问索引:
- 必须使用传统
for循环。当你需要知道当前是第几个元素,或者需要根据索引做一些特殊操作(如替换元素list.set(i, newValue),或反向遍历)时,只能用它。 - 例如:
for (int i = list.size() - 1; i >= 0; i--) { ... }
- 必须使用传统
简单来说,遍历不操作,用for-each;遍历要删除,用Iterator;遍历要用索引,用传统for。 理解了这个原则,你就能在不同场景下选择最合适的遍历方式。