传统 for 循环
这是 Java 最基础、最古老的循环结构,功能非常强大和灵活。

语法结构
for (初始化表达式; 循环条件; 更新表达式) {
// 循环体:要执行的代码
}
三个组成部分详解
-
初始化表达式:
- 在循环开始时,只执行一次。
- 通常用于声明一个循环计数器变量(如
int i = 0)。 - 可以声明多个变量,但类型必须相同(用逗号分隔)。
-
循环条件:
- 在每次循环迭代之前都会被检查。
- 如果条件为
true,则执行循环体;如果为false,则循环终止。 - 如果没有提供条件,它默认为
true,这会导致无限循环。
-
更新表达式:
- 在每次循环体执行完毕之后执行。
- 通常用于更新计数器变量的值(如
i++或i--)。
示例:遍历数组
String[] fruits = {"Apple", "Banana", "Cherry"};
// 初始化 i = 0; 只要 i < fruits.length 的长度就继续循环; 每次循环后 i++
for (int i = 0; i < fruits.length; i++) {
System.out.println("Fruit " + i + ": " + fruits[i]);
}
输出:

Fruit 0: Apple
Fruit 1: Banana
Fruit 2: Cherry
传统 for 循环的特点
-
优点:
- 功能强大:不仅可以从前向后遍历,还可以从后向前遍历,或者按自定义步长遍历。
- 精确控制:可以知道当前元素在集合或数组中的索引,这在需要根据索引进行操作时非常有用。
-
缺点:
- 代码冗长:语法相对繁琐,容易出错(边界条件
<=和<的混淆)。 - 可读性稍差:当只需要遍历所有元素而不关心索引时,传统
for循环的语法显得有些“啰嗦”。
- 代码冗长:语法相对繁琐,容易出错(边界条件
for-each 循环 (增强型 for 循环)
for-each 循环是在 Java 5 中引入的,专门用于简化集合和数组的遍历,它也被称为 enhanced for loop。
语法结构
for (元素类型 元素变量 : 遍历对象) {
// 循环体:对元素变量进行操作
}
元素类型:集合或数组中单个元素的类型。元素变量:在每次迭代中,会自动从遍历对象中取出一个元素,赋值给这个变量。遍历对象:任何实现了Iterable接口的对象(如List,Set)或者是一个数组。
示例:遍历数组
String[] fruits = {"Apple", "Banana", "Cherry"};
// 对于 fruits 数组中的每一个元素,将其赋值给 fruit 变量
for (String fruit : fruits) {
System.out.println("Fruit: " + fruit);
}
输出:

Fruit: Apple
Fruit: Banana
Fruit: Cherry
for-each 循环的特点
-
优点:
- 代码简洁:语法非常简单,易于阅读和理解。
- 不易出错:无需关心索引、边界条件等细节,减少了出错的可能性。
- 专注于元素本身:让你可以更专注于对当前元素的操作,而不是如何获取下一个元素。
-
缺点:
- 无法获取索引:这是它最大的限制,如果你需要知道当前元素的索引位置,
for-each循环无能为力。 - 无法修改集合本身:在遍历
Collection(如List或Set)时,不能使用for-each循环来添加或删除元素,这会抛出ConcurrentModificationException异常。- 注意:你可以通过
for-each循环中的元素变量来修改集合中元素的(前提是元素是可变对象),但不能修改集合的结构(增删元素)。
- 注意:你可以通过
- 只能单向遍历:只能从前往后遍历,无法反向或跳跃遍历。
- 无法获取索引:这是它最大的限制,如果你需要知道当前元素的索引位置,
for-each vs 传统 for 循环:如何选择?
这是一个非常常见的问题,以下是选择它们的指导原则:
| 特性 | 传统 for 循环 |
for-each 循环 |
|---|---|---|
| 代码简洁性 | 较差,语法繁琐 | 极佳,非常简洁 |
| 获取索引 | 可以,通过循环变量 i |
不可以 |
| 反向遍历 | 可以 (从 length-1 到 0) |
不可以 |
| 修改集合结构 | 可以 (使用 ListIterator 或 remove 方法时需小心) |
不可以,会抛出异常 |
| 遍历所有元素 | 可以 | 是它的主要设计目的,非常方便 |
| 适用场景 | 需要索引、需要反向遍历、需要修改集合结构 | 只需遍历所有元素,不关心索引 |
何时使用 for-each 循环?
- 首选场景:当你只需要遍历一个集合或数组中的每一个元素,并且不需要知道它们的索引时。
- 示例:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); for (String name : names) { System.out.println(name); }
何时使用传统 for 循环?
-
需要索引时:
// 打印数组中每个元素及其索引 for (int i = 0; i < fruits.length; i++) { System.out.println("Index " + i + " is " + fruits[i]); } -
需要反向遍历时:
// 从后向前打印数组 for (int i = fruits.length - 1; i >= 0; i--) { System.out.println(fruits[i]); } -
需要在遍历过程中修改集合时:
// 安全地删除所有偶数元素 List<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6)); // 必须使用传统的 for 循环和索引,或者使用 Iterator for (int i = 0; i < numbers.size(); i++) { if (numbers.get(i) % 2 == 0) { numbers.remove(i); i--; // 因为删除了一个元素,索引需要回退一位 } } System.out.println(numbers); // 输出: [1, 3, 5]
Java 8+ 的新选择:Stream API
对于现代 Java 开发,如果只是想对集合进行遍历和处理,Stream API 通常是更优雅、更强大的选择。
示例:使用 Stream 遍历
import java.util.Arrays;
import java.util.List;
List<String> fruits = Arrays.asList("Apple", "Banana", "Cherry");
// 1. 简单遍历
fruits.stream().forEach(fruit -> System.out.println("Fruit: " + fruit));
// 2. 带条件的遍历(打印长度大于5的单词)
fruits.stream()
.filter(fruit -> fruit.length() > 5)
.forEach(fruit -> System.out.println("Long fruit: " + fruit));
Stream API 的优势:
- 函数式编程:代码更声明式,易于阅读。
- 链式调用:可以轻松地组合多个操作(如
filter,map,sorted)。 - 并行处理:只需将
stream()换成parallelStream(),即可轻松实现并行计算,提高性能。
| 循环类型 | 核心思想 | 优点 | 缺点 | 最佳实践 |
|---|---|---|---|---|
传统 for |
基于索引的控制 | 灵活,可获取索引,可反向/修改集合 | 冗长,易出错 | 当需要索引、反向遍历或修改集合时使用。 |
for-each |
基于元素的迭代 | 简洁,易读,不易出错 | 无法获取索引,不能修改集合 | 默认选择,当只需要遍历所有元素时,优先使用它。 |
| Stream API | 声明式数据处理 | 函数式,可链式调用,支持并行 | 学习曲线稍陡,性能开销极小 | 现代 Java 的首选,尤其适合复杂的集合数据处理。 |
记住这个简单的原则:如果只需要遍历,就用 for-each;如果需要索引或更复杂的控制,就用传统 for 循环;如果需要处理数据,优先考虑 Stream API。
