在 Java 中,遍历 List 有多种方式,for 循环主要有两种形式:

- 经典的
for循环 (索引循环) - 增强型
for循环 (For-Each 循环)
我们还会提到 Java 8 引入的 forEach 方法,因为它在功能上与 for 循环类似,是现代 Java 中非常推荐的遍历方式。
经典的 for 循环 (基于索引)
这是最传统、最基础的循环方式,通过 List 的 size() 和 get(index) 方法来访问每个元素。
语法
for (int i = 0; i < list.size(); i++) {
// 通过 list.get(i) 获取元素
Element element = list.get(i);
// ... 处理元素
}
示例
import java.util.ArrayList;
import java.util.List;
public class ClassicForLoopExample {
public static void main(String[] args) {
// 创建一个 List 并添加一些元素
List<String> fruits = new ArrayList<>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Cherry");
System.out.println("--- 使用经典 for 循环遍历 ---");
// 使用 for 循环遍历
for (int i = 0; i < fruits.size(); i++) {
String fruit = fruits.get(i);
System.out.println("水果: " + fruit);
}
}
}
优点
- 可以访问索引:如果你需要在循环体内知道当前元素的索引
i,这种方式非常方便。 - 可以按任意顺序遍历:你可以轻松地修改循环的起始和结束点,或者反向遍历(
for (int i = list.size() - 1; i >= 0; i--))。 - 可以修改 List:在遍历过程中,可以安全地通过索引
remove元素(但需要注意,这可能会影响后续的索引,通常从后往前删除更安全)。
缺点
- 代码冗长:相比其他方式,代码量更多。
- 容易出错:如果手动管理索引,可能会犯
IndexOutOfBoundsException等错误。 - 性能问题 (特定情况下):对于
LinkedList,get(index)方法的时间复杂度是 O(n),因为需要从头开始遍历,在LinkedList上使用这种循环效率很低,但对于ArrayList,get(index)的时间复杂度是 O(1),效率很高。
增强型 for 循环 (For-Each 循环)
Java 5 引入了这种更简洁、更安全的循环语法,专门用于遍历集合和数组。
语法
for (Element element : list) {
// 直接使用 element,无需关心索引
// ... 处理元素
}
这里的 Element 是 List 中元素的类型。

示例
import java.util.ArrayList;
import java.util.List;
public class EnhancedForLoopExample {
public static void main(String[] args) {
List<String> fruits = new ArrayList<>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Cherry");
System.out.println("--- 使用增强型 for 循环遍历 ---");
// 使用增强型 for 循环遍历
for (String fruit : fruits) {
System.out.println("水果: " + fruit);
}
}
}
优点
- 代码简洁:语法非常简单,易于阅读。
- 不易出错:无需担心索引越界或循环条件错误。
- 性能良好:对于
ArrayList和LinkedList都有良好的性能,它在内部通过迭代器实现,对于ArrayList是高效的,对于LinkedList也比索引循环更优。
缺点
- 无法获取索引:如果你需要元素的索引,这种方式就不方便了(通常需要额外声明一个计数器变量)。
- 遍历过程中不能安全地修改 List:这是最重要的一个限制! 如果你尝试在
for-each循环中调用list.remove()方法,会抛出ConcurrentModificationException异常。- 原因:
for-each循环内部使用了一个隐藏的迭代器,当你调用list.remove()时,它会直接修改底层数组,而迭代器检测到自己的预期状态被破坏,就会立即抛出异常。
- 原因:
Java 8+ 的 forEach 方法
这是函数式编程风格的遍历方式,非常现代和灵活,它接受一个 Consumer 函数式接口作为参数。
语法
list.forEach(element -> {
// ... 处理元素
});
或者,如果处理逻辑简单,可以使用方法引用:
list.forEach(System.out::println);
示例
import java.util.ArrayList;
import java.util.List;
public class ForEachMethodExample {
public static void main(String[] args) {
List<String> fruits = new ArrayList<>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Cherry");
System.out.println("--- 使用 forEach 方法遍历 ---");
// 使用 forEach 方法遍历
fruits.forEach(fruit -> {
System.out.println("水果: " + fruit);
});
System.out.println("\n--- 使用方法引用遍历 ---");
// 使用方法引用 (更简洁)
fruits.forEach(System.out::println);
}
}
优点
- 极其简洁:一行代码即可完成遍历和操作,特别是配合方法引用时。
- 函数式风格:非常适合与 Lambda 表达式和 Stream API 结合使用,是现代 Java 开发的首选。
- 线程安全:
forEach方法使用的是集合自身的迭代器,而不是for-each循环那样的快速失败迭代器,这意味着在单线程环境下,如果你使用一个外部迭代器(显式创建Iterator),可以在遍历时安全地删除元素(但这仍然不推荐,因为会破坏其他正在进行的遍历)。
缺点
- 无法使用
break或continue:forEach是一种声明式操作,你不能像在传统for循环中那样用break提前退出循环,或用continue跳过当前元素,如果需要这些控制流,必须使用传统的for循环。 - 性能开销:Lambda 表达式会带来微小的性能开销,但在绝大多数应用场景下可以忽略不计。
- 修改 List 的风险:和
for-each循环一样,在forEach的 Lambda 表达式中直接调用list.remove()同样是不安全的,也可能导致ConcurrentModificationException。
总结与选择建议
| 特性 | 经典 for 循环 |
增强型 for 循环 |
forEach 方法 |
|---|---|---|---|
| 语法 | for (int i=0; i<list.size(); i++) |
for (Element e : list) |
list.forEach(e -> ...) |
| 可读性 | 较低 | 高 | 非常高(尤其方法引用) |
| 获取索引 | 是 | 否(需额外变量) | 否(需额外变量) |
| 修改 List | 可以 (需谨慎) | 不可以 (会抛异常) | 不可以 (会抛异常) |
break/continue |
可以 | 不可以 | 不可以 |
| 适用场景 | 需要索引、需要反向遍历、需要精确控制循环流程 | 简单、安全的遍历,不需要索引 | 现代Java,函数式风格,简洁的遍历和操作 |
如何选择?
-
优先选择
forEach方法:- 如果你使用的是 Java 8 或更高版本,并且只是简单地遍历
List中的元素并进行操作(如打印、计算等),forEach是最佳选择,它最简洁、最符合现代编程范式。
- 如果你使用的是 Java 8 或更高版本,并且只是简单地遍历
-
其次选择增强型
for循环 (for-each):
(图片来源网络,侵删)- 如果你使用的是 Java 5/6/7,或者你的团队尚未全面采用 Java 8+ 的特性。
- 当你只需要遍历元素,不需要索引,并且确定不会在循环内修改
List时,for-each是一个安全、清晰的选择。
-
最后选择经典
for循环:- 当你需要元素的索引时(获取前 N 个元素,或者需要根据索引进行其他计算)。
- 当你需要在遍历过程中安全地删除元素时(通常建议从后往前删除)。
- 当你需要使用
break或continue来控制流程时。 - 当你遍历的是
ArrayList并且对性能有极致要求时(for-each和forEach在这里性能几乎一样,但都比手动索引循环稍慢一点点,但可读性更重要)。
最佳实践:在 95% 的情况下,你只需要遍历元素。在 Java 8+ 中,forEach 是首选;在旧版本中,for-each 是首选,只在明确需要索引或特殊控制流时,才使用经典的 for 循环。
