核心概念
传统 for 循环 (Classic for loop)
这是最基础、最通用的循环结构,它基于一个索引来遍历数据,你可以把它想象成一个“计数器”,从某个起点开始,每次增加或减少,直到达到终点。

(图片来源网络,侵删)
语法结构:
for (初始化; 循环条件; 更新) {
// 循环体 (要执行的代码)
}
- 初始化: 只在循环开始前执行一次,通常用于初始化一个计数器变量(如
int i = 0)。 - 循环条件: 每次循环开始前都会检查,如果为
true,则执行循环体;如果为false,则循环结束。 - 更新: 每次循环体执行完毕后执行,通常用于更新计数器(如
i++)。
示例:遍历一个数组
String[] fruits = {"Apple", "Banana", "Cherry"};
System.out.println("--- 使用传统 for 循环 ---");
for (int i = 0; i < fruits.length; i++) {
System.out.println("索引 " + i + ": " + fruits[i]);
}
// 输出:
// --- 使用传统 for 循环 ---
// 索引 0: Apple
// 索引 1: Banana
// 索引 2: Cherry
for-each 循环 (Enhanced for loop)
也称为“增强型 for 循环”或“for-in”循环,它专门用于遍历集合或数组,语法更简洁,无需关心索引的初始化、边界和更新。
语法结构:

(图片来源网络,侵删)
for (元素类型 元素变量 : 遍历对象) {
// 循环体 (要执行的代码)
}
- 元素类型: 遍历对象中每个元素的类型。
- 元素变量: 一个临时变量,在每次循环中会自动接收当前元素的值。
- 遍历对象: 任何实现了
Iterable接口(如List,Set)或是一个数组。
示例:遍历一个数组
String[] fruits = {"Apple", "Banana", "Cherry"};
System.out.println("--- 使用 for-each 循环 ---");
for (String fruit : fruits) {
System.out.println("水果: " + fruit);
}
// 输出:
// --- 使用 for-each 循环 ---
// 水果: Apple
// 水果: Banana
// 水果: Cherry
核心区别与对比
| 特性 | 传统 for 循环 |
for-each 循环 |
|---|---|---|
| 语法 | for (int i=0; i<arr.length; i++) |
for (String item : arr) |
| 可读性 | 较复杂,需要理解三个部分 | 非常简洁,可读性高 |
| 访问方式 | 通过索引访问元素 (arr[i]) |
直接获取元素本身 (item) |
| 修改能力 | 可以修改集合/数组本身 ( list.add(), arr[i] = "new") |
不能直接修改集合/数组本身 (可以修改元素的值,如果元素是对象) |
| 获取索引 | 非常方便,i 就是当前索引 |
没有直接的方式获取当前元素的索引 |
| 适用范围 | 非常通用 遍历数组/集合 需要索引时 需要反向遍历时 需要按特定步长遍历时 |
主要用于遍历 数组 实现 Iterable 接口的集合(List, Set 等)当你只需要元素值,不需要索引时 |
| 性能 | 在某些特定场景下(如遍历数组)可能比 for-each 稍快,因为 for-each 内部会创建一个迭代器对象。 |
对于 List(如 ArrayList),通过索引访问(get(i))比迭代器访问稍快,但对于 LinkedList,迭代器性能更优,通常性能差异可以忽略。 |
深入探讨与代码示例
修改元素 vs. 修改集合
这是两者最关键的区别之一。
a) 传统 for 循环:可以修改集合和元素
import java.util.ArrayList;
import java.util.List;
List<String> names = new ArrayList<>(List.of("Alice", "Bob", "Charlie"));
// 修改集合:添加新元素
for (int i = 0; i < names.size(); i++) {
if (i == 1) {
names.add("David"); // 在遍历过程中添加元素
}
}
System.out.println("修改集合后: " + names); // [Alice, Bob, Charlie, David]
// 修改元素
for (int i = 0; i < names.size(); i++) {
names.set(i, names.get(i).toUpperCase());
}
System.out.println("修改元素后: " + names); // [ALICE, BOB, CHARLIE, DAVID]
注意:在遍历 List 时使用 for 循环并调用 add() 或 remove() 方法,很容易因为索引错位而导致错误,更安全的做法是使用迭代器的 remove() 方法。
b) for-each 循环:可以修改元素,但不能直接修改集合
List<String> names = new ArrayList<>(List.of("Alice", "Bob", "Charlie"));
// 尝试修改集合:会抛出 ConcurrentModificationException 异常
try {
for (String name : names) {
if (name.equals("Bob")) {
names.add("David"); // 在遍历过程中修改集合结构
}
}
} catch (Exception e) {
System.out.println("尝试修改集合时出错: " + e);
}
// 输出: 尝试修改集合时出错: java.util.ConcurrentModificationException
// 修改元素(如果元素是可变的)
// 如果 names 里的元素是自定义的 Person 对象,可以修改其属性
for (String name : names) {
// name 是一个 String,它是不可变的。
// name = "NEW_NAME"; // 这行代码不会改变 List 中的 "Alice",它只是让局部变量 name 指向了一个新的字符串对象。
System.out.println("尝试修改局部变量: " + name);
}
System.out.println("尝试修改元素后: " + names); // [Alice, Bob, Charlie] - 没有变化
如何在 for-each 中获取索引?
for-each 循环本身不提供索引,如果需要索引,有以下几种方法:
使用传统 for 循环(最直接)
List<String> fruits = List.of("Apple", "Banana", "Cherry");
for (int i = 0; i < fruits.size(); i++) {
System.out.println("索引 " + i + ": " + fruits.get(i));
}
使用外部计数器
List<String> fruits = List.of("Apple", "Banana", "Cherry");
int index = 0;
for (String fruit : fruits) {
System.out.println("索引 " + index + ": " + fruit);
index++;
}
Java 8+ 使用 Stream API (函数式风格)
import java.util.List;
import java.util.stream.IntStream;
List<String> fruits = List.of("Apple", "Banana", "Cherry");
IntStream.range(0, fruits.size())
.forEach(i -> System.out.println("索引 " + i + ": " + fruits.get(i)));
总结与最佳实践
何时使用传统 for 循环?
- 需要索引时:这是最主要的原因,你需要根据索引来访问另一个数组或集合中的对应元素。
int[] values = {10, 20, 30}; String[] labels = {"A", "B", "C"}; for (int i = 0; i < values.length; i++) { System.out.println(labels[i] + ": " + values[i]); } - 需要反向遍历时:
for (int i = arr.length - 1; i >= 0; i--) { System.out.println(arr[i]); } - 需要按特定步长遍历时:
for (int i = 0; i < 100; i += 2) { // 每次跳过2个 System.out.println(i); } - 需要在遍历过程中修改集合的结构(如删除元素,最好使用迭代器的
remove()方法,但for循环是唯一能直接通过索引操作的结构)。
何时使用 for-each 循环?
- 遍历集合或数组,且不需要索引时:这是
for-each的核心应用场景,代码更简洁、更安全、可读性更高。for (String line : fileLines) { process(line); } - 当你只关心集合中的元素本身,而不关心它们的位置或顺序时。
- 代码优先选择
for-each:在不确定的情况下,优先使用for-each,因为它更安全、更不易出错,只有在明确需要索引或反向遍历时,才切换到传统for循环。
黄金法则:
如果只需要读出集合中的每一个元素,就使用
for-each,如果需要索引、需要反向遍历、或者需要在遍历时修改集合的结构,则使用传统的for循环。
