在Java开发中,集合是最常用的数据结构载体,而遍历集合几乎出现在每一段业务代码里。不同的集合类型与遍历方式在性能、可读性和安全性上差异明显,选错方式可能导致代码抛出异常或运行缓慢。
一、普通for循环(基于下标)
普通for循环通过索引依次访问集合元素,语法直观,是很多初学者最先接触的遍历方式。它依赖于集合提供按索引取值的能力,因此最适合底层用数组实现的ArrayList这类随机访问友好的列表。
下面以ArrayList为例展示基本用法:
import java.util.ArrayList;
import java.util.List;
public class ForIndexDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("苹果");
list.add("香蕉");
list.add("橙子");
// 普通for循环基于下标遍历
for (int i = 0; i < list.size(); i++) {
String item = list.get(i);
System.out.println(item);
}
}
}
这种方式的优势在于代码简单、易于控制索引,且在ArrayList上时间复杂度为O(n)。但如果集合是LinkedList,每次调用get(i)都要从头节点移动指针,整体复杂度退化为O(n²),数据量大时性能极差。
另外,在普通for循环中若直接调用list.remove(i)删除元素,会造成索引错乱,后续元素被跳过或抛出IndexOutOfBoundsException,因此并不适合边遍历边修改结构。
二、Iterator迭代器遍历
Iterator是Java集合框架提供的统一遍历接口,所有实现了Collection的类都可以通过iterator()方法获取迭代器。它隐藏了底层数据结构差异,以一致的方式访问元素。
使用Iterator时,推荐使用其自带的remove()方法在遍历中删除元素,这样可以避免并发修改异常。示例代码如下:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class IteratorDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("苹果");
list.add("香蕉");
list.add("坏梨");
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String item = it.next();
if ("坏梨".equals(item)) {
it.remove(); // 安全删除
} else {
System.out.println(item);
}
}
}
}
Iterator的核心价值在于fail-fast机制:如果在迭代过程中集合结构被非迭代器方式修改,会抛出ConcurrentModificationException,从而及早暴露错误。相比之下,它比普通for循环更安全。
不过Iterator语法稍显冗长,且只能单向移动,不支持向后遍历。在只需要读取而不删除的场景中,开发者往往更倾向使用更简洁的写法。
三、增强for循环(for-each)
增强for循环从Java 5引入,语法简洁,底层实际被编译器转换为Iterator操作。它适用于所有实现了Iterable接口的集合与数组。
以下代码等价于使用Iterator遍历,但可读性更高:
import java.util.ArrayList;
import java.util.List;
public class ForEachDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("苹果");
list.add("香蕉");
for (String item : list) {
System.out.println(item);
}
}
}
增强for循环避免了手动调用next()和hasNext(),减少编码错误。但要注意,在增强for循环体内不能直接删除元素,否则会触发ConcurrentModificationException,因为删除操作没经过迭代器自身方法。
如果遍历逻辑包含复杂的索引计算或逆向访问,增强for循环就无法满足,此时仍需退回普通for或ListIterator。
四、forEach方法与Lambda表达式
Java 8之后,Iterable接口新增了forEach(Consumer)默认方法,配合Lambda表达式可写出更声明式的遍历代码。它适合纯消费数据的场景。
示例展示如何使用forEach输出集合内容:
import java.util.Arrays;
import java.util.List;
public class ForEachMethodDemo {
public static void main(String[] args) {
List<String> list = Arrays.asList("苹果", "香蕉", "橙子");
list.forEach(item -> System.out.println(item));
// 方法引用简写
list.forEach(System.out::println);
}
}
forEach方法内部依旧基于Iterator,但不能使用break或continue控制流程,也不支持在Lambda中修改外部非final变量。对于简单打印、批量处理非常方便。
当遍历过程需要复杂分支或提前退出时,Lambda风格反而会降低清晰度,这时传统循环更合适。
五、Stream API遍历
Stream API提供了函数式数据处理能力,常用于过滤、映射、归约等操作后再遍历。它不仅能遍历,还能并行化处理。
下面示例先过滤再遍历:
import java.util.Arrays;
import java.util.List;
public class StreamDemo {
public static void main(String[] args) {
List<String> list = Arrays.asList("苹果", "香蕉", "橙子");
list.stream()
.filter(item -> item.length() > 1)
.forEach(System.out::println);
}
}
Stream的优势在于链式调用与并行流(parallelStream())带来的多核利用。但Stream本身有创建与拆箱开销,在极小数据集上未必比普通循环快。
此外,Stream操作分为中间与终止操作,只有终止操作触发才会真正遍历数据源,这种惰性求值特性有助于优化执行计划。
六、不同遍历方式如何选择
实际选型应综合集合类型、操作目的与性能要求。若使用ArrayList且只需读取,普通for循环或增强for均可;若使用LinkedList,务必用Iterator或增强for避免性能陷阱。
下表简要对比常见方式:
| 方式 | 适用集合 | 可否删除 | 性能特点 |
|---|---|---|---|
| 普通for | ArrayList | 易错 | 随机访问快,链表慢 |
| Iterator | 所有Collection | 安全 | 统一稳定 |
| 增强for | Iterable | 不可 | 简洁但有限制 |
| forEach | Iterable | 不可 | 声明式风格 |
| Stream | Collection | 不可 | 支持并行与链式 |
理解这些差异后,便能在代码中主动规避低效写法,例如不在LinkedList上使用下标循环,不在增强for里做结构性删除。合理的遍历方式不仅提升运行效率,也增强代码可维护性。