for 循环
"遍历是程序员的日常——遍历列表、遍历数组、遍历文件的每一行。" —— 小崔在开发飞翔科技库存管理系统时感叹,"for 循环是所有循环中最精炼的,初始化、条件、迭代一气呵成,结构紧凑得像个完美的三段论。"
概述
for 循环是 Java 中最常用的循环结构。它将初始化、条件判断、迭代更新三个要素集中在一个语句中,结构紧凑、意图明确。当你明确知道循环次数或遍历范围时,for 循环是首选。
一、基本 for 循环
语法格式
for (初始化语句; 条件表达式; 迭代语句) {
// 循环体
}
循环三要素
| 要素 | 执行时机 | 执行次数 | 说明 |
|---|---|---|---|
| 初始化语句 | 循环开始前 | 1 次 | 声明并初始化循环变量 |
| 条件表达式 | 每次迭代前 | N+1 次(最后一次判断为 false) | 返回 boolean,true 则进入循环体 |
| 迭代语句 | 每次循环体执行完 | N 次 | 更新循环变量,使循环趋向终止 |
N = 循环体执行的次数。
执行流程
关键点
| 关键点 | 说明 |
|---|---|
| 三个部分均可省略 | 省略条件表达式则默认为 true,形成无限循环:for (;;) |
| 初始化语句可声明多个变量 | 但必须同类型:for (int i = 0, j = n; i < j; i++, j--) |
| 迭代语句可执行多个操作 | 用逗号分隔:for (int i = 0; i < n; i++, count++) |
| 循环变量作用域 | 在初始化中声明的变量,作用域仅限于 for 循环内部 |
示例一:商品库存统计
飞翔科技电商平台需要统计各品类商品的库存总量,朱璐正在编写库存盘点模块:
/**
* 飞翔科技库存盘点——基本 for 循环
*/
public class InventoryCount {
public static void main(String[] args) {
// 各品类商品库存量
int[] stock = {120, 85, 200, 67, 153, 0, 42};
String[] categories = {
"手机", "电脑", "平板", "耳机", "手表", "音箱", "充电器"
};
int totalStock = 0;
int maxStock = stock[0];
int maxIndex = 0;
System.out.println("===== 飞翔科技库存盘点 =====");
for (int i = 0; i < stock.length; i++) {
System.out.printf(" %s:%d 件\n", categories[i], stock[i]);
totalStock += stock[i];
if (stock[i] > maxStock) {
maxStock = stock[i];
maxIndex = i;
}
}
System.out.println("------------------------------");
System.out.println("库存总量:" + totalStock + " 件");
System.out.println("库存最多:" + categories[maxIndex]
+ "(" + maxStock + " 件)");
System.out.println("盘点完毕。");
}
}
运行输出:
===== 飞翔科技库存盘点 =====
手机:120 件
电脑:85 件
平板:200 件
耳机:67 件
手表:153 件
音箱:0 件
充电器:42 件
------------------------------
库存总量:667 件
库存最多:平板(200 件)
盘点完毕。
二、嵌套 for 循环
一个 for 循环的循环体中包含另一个 for 循环,称为嵌套循环。外层循环每执行一次,内层循环执行完整一轮。
执行流程
总执行次数 = 外层次数 × 内层次数。时间复杂度通常为 O(m × n)。
示例:学生成绩二维表
小崔需要为飞翔科技联合办学项目的学生生成成绩报表。每个学生有多门课程的成绩,需要计算个人总分和单科平均分:
/**
* 飞翔科技学生成绩报表——嵌套 for 循环
*/
public class StudentScoreReport {
public static void main(String[] args) {
// 学生姓名
String[] students = {"张三", "李四", "王五", "赵六"};
// 课程名称
String[] courses = {"Java基础", "数据结构", "数据库", "操作系统"};
// 成绩二维数组:行=学生,列=课程
int[][] scores = {
{88, 92, 79, 85}, // 张三
{76, 81, 90, 88}, // 李四
{95, 89, 93, 91}, // 王五
{60, 58, 72, 65} // 赵六
};
System.out.println("===== 飞翔科技学生成绩报表 =====");
System.out.println();
// 外层循环:遍历学生
for (int i = 0; i < students.length; i++) {
int personalTotal = 0;
System.out.print("学生:" + students[i] + " | 成绩:");
// 内层循环:遍历课程
for (int j = 0; j < courses.length; j++) {
System.out.print(courses[j] + "=" + scores[i][j] + " ");
personalTotal += scores[i][j];
}
double personalAvg = personalTotal / (double) courses.length;
System.out.println();
System.out.println(" → 总分:" + personalTotal
+ " | 平均分:" + String.format("%.1f", personalAvg));
System.out.println();
}
// 单科平均分统计
System.out.println("--- 单科平均分 ---");
for (int j = 0; j < courses.length; j++) {
int subjectTotal = 0;
for (int i = 0; i < students.length; i++) {
subjectTotal += scores[i][j];
}
double subjectAvg = subjectTotal / (double) students.length;
System.out.printf(" %s:%.1f\n", courses[j], subjectAvg);
}
System.out.println("报表生成完毕。");
}
}
运行输出:
===== 飞翔科技学生成绩报表 =====
学生:张三 | 成绩:Java基础=88 数据结构=92 数据库=79 操作系统=85
→ 总分:344 | 平均分:86.0
学生:李四 | 成绩:Java基础=76 数据结构=81 数据库=90 操作系统=88
→ 总分:335 | 平均分:83.8
学生:王五 | 成绩:Java基础=95 数据结构=89 数据库=93 操作系统=91
→ 总分:368 | 平均分:92.0
学生:赵六 | 成绩:Java基础=60 数据结构=58 数据库=72 操作系统=65
→ 总分:255 | 平均分:63.8
--- 单科平均分 ---
Java基础:79.8
数据结构:80.0
数据库:83.5
操作系统:82.3
报表生成完毕。
三、增强 for 循环(for-each)
JDK 5 引入了增强 for 循环(Enhanced for Loop,又称 for-each),用于遍历数组或 Iterable 集合,语法更简洁,但灵活性有限。
语法格式
for (元素类型 变量名 : 数组或Iterable集合) {
// 使用变量名访问当前元素
}
对比:传统 for vs 增强 for
| 维度 | 传统 for | 增强 for |
|---|---|---|
| 语法简洁度 | 需要索引变量 | 极简,声明即用 |
| 访问索引 | 可通过 i 获取 | 无法获取 |
| 修改元素 | 可通过索引修改数组元素 | 修改变量不影响原数组 |
| 反向/跳跃遍历 | 支持 | 不支持 |
| 适用对象 | 数组、任何需要计数器的场景 | 数组 + Iterable(Collection 等) |
示例:批量发送通知
Frank 需要向一批测试用户发送内测邀请,使用增强 for 循环遍历用户列表:
/**
* 飞翔科技内测邀请——增强 for 循环
*/
public class BetaInvitation {
public static void main(String[] args) {
String[] testUsers = {
"大翔", "白歌", "小崔", "朱璐", "Frank"
};
String productName = "飞翔云笔记 v2.0";
int inviteCount = 0;
System.out.println("===== 飞翔科技内测邀请 =====");
System.out.println("产品:" + productName);
System.out.println("内测用户列表:");
for (String user : testUsers) {
System.out.println(" 正在向 " + user + " 发送内测邀请...");
// 模拟发送操作
inviteCount++;
}
System.out.println("------------------------------");
System.out.println("已向 " + inviteCount + " 位用户发送内测邀请。");
System.out.println("内测邀请全部发出。");
}
}
运行输出:
===== 飞翔科技内测邀请 =====
产品:飞翔云笔记 v2.0
内测用户列表:
正在向 大翔 发送内测邀请...
正在向 白歌 发送内测邀请...
正在向 小崔 发送内测邀请...
正在向 朱璐 发送内测邀请...
正在向 Frank 发送内测邀请...
------------------------------
已向 5 位用户发送内测邀请。
内测邀请全部发出。
增强 for 的限制
// ❌ 增强 for 不能用于以下场景
int[] arr = {1, 2, 3, 4, 5};
// 1. 无法获取索引
for (int x : arr) {
// 无法知道 x 是第几个元素
}
// 2. 无法修改原数组
for (int x : arr) {
x = x * 2; // 只修改了局部变量 x,不影响 arr
}
// 3. 无法反向遍历或跳跃遍历(只能从头到尾)
四、for 循环中的 break 与 continue
break 和 continue 在 for 循环中有特殊的行为,理解它们的区别至关重要:
| 关键字 | 行为 | 对迭代语句的影响 |
|---|---|---|
| break | 立即终止整个循环 | 不执行 |
| continue | 跳过当前迭代的剩余部分 | 仍然执行迭代语句,然后重新判断条件 |
关键点:在 for 循环中,
continue之后迭代语句仍然会执行(这是与 while 循环的一个重要区别)。
深度原理:for 循环的字节码实现
基本 for 循环的字节码
Java 源码:
int sum = 0;
for (int i = 1; i <= 10; i++) {
sum += i;
}
反编译字节码:
0: iconst_0 // sum = 0
1: istore_1
2: iconst_1 // i = 1
3: istore_2
4: iload_2 // 加载 i
5: bipush 10 // 加载常量 10
7: if_icmpgt 19 // 如果 i > 10,跳转到偏移量 19(循环结束)
10: iload_1 // 加载 sum
11: iload_2 // 加载 i
12: iadd // sum + i
13: istore_1 // sum = sum + i
14: iinc 2, 1 // i++(i 自增 1)
17: goto 4 // 跳回偏移量 4(条件判断)
19: return
关键指令:
| 指令 | 含义 |
|---|---|
iinc index, const | 将局部变量表中索引为 index 的 int 变量增加 const |
if_icmpgt offset | 比较栈顶两个 int,若 value1 > value2 则跳转 |
增强 for 循环的字节码
增强 for 循环遍历数组时,编译器将其转换为传统 for 循环:
// 源码
for (String s : arr) { ... }
// 编译器等价转换
for (int i = 0; i < arr.length; i++) {
String s = arr[i];
...
}
遍历 Iterable 集合时,编译器转换为迭代器模式:
// 源码
for (String s : list) { ... }
// 编译器等价转换
for (Iterator<String> it = list.iterator(); it.hasNext(); ) {
String s = it.next();
...
}
易错场景
反例一:for 循环分号陷阱(空循环体)
这是初学者最容易踩的坑——在 for 的右括号后多加了一个分号:
// ❌ 错误:分号构成了空循环体
int sum = 0;
for (int i = 1; i <= 10; i++); { // ← 注意这个分号!
sum += i;
}
System.out.println(sum);
运行输出:
11
原因分析:分号 ; 本身是一条空语句,它成为了 for 循环的循环体。花括号 { ... } 是一个独立代码块,只在循环结束后执行一次。此时 i 的值是 11(循环退出条件为 i <= 10 为 false),所以 sum = 11。
// ✅ 正确:去掉分号
int sum = 0;
for (int i = 1; i <= 10; i++) {
sum += i;
}
System.out.println(sum); // 55
反例二:循环变量作用域误解
// ❌ 编译错误:i 的作用域仅限于 for 循环内部
for (int i = 0; i < 5; i++) {
System.out.println(i);
}
System.out.println("最后的 i = " + i); // cannot find symbol: i
// ✅ 正确:如果需要在循环外使用,在循环外声明
int i;
for (i = 0; i < 5; i++) {
System.out.println(i);
}
System.out.println("最后的 i = " + i); // 5
反例三:嵌套循环中的 break 误区
// ❌ 错误:break 只能跳出最内层循环
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
if (i * j > 4) {
break; // 只跳出内层 j 循环,外层 i 循环继续
}
System.out.println("i=" + i + ", j=" + j);
}
}
运行输出:
i=1, j=1
i=1, j=2
i=1, j=3
i=2, j=1
i=2, j=2
i=3, j=1
需要使用带标签的 break 才能跳出外层循环(详见
break与continue.md)。
反例四:增强 for 中的并发修改
// ❌ 运行时错误:ConcurrentModificationException
import java.util.ArrayList;
import java.util.List;
List<String> names = new ArrayList<>();
names.add("大翔");
names.add("白歌");
names.add("小崔");
for (String name : names) {
if ("小崔".equals(name)) {
names.remove(name); // 在增强 for 中修改集合!
}
}
// ✅ 正确:使用 Iterator 的 remove() 方法
import java.util.Iterator;
Iterator<String> it = names.iterator();
while (it.hasNext()) {
String name = it.next();
if ("小崔".equals(name)) {
it.remove(); // 安全删除
}
}
反例五:浮点数作为循环变量
// ❌ 危险:浮点精度误差可能导致循环次数不确定
for (double d = 0.0; d != 1.0; d += 0.1) {
System.out.println(d);
}
// 可能永远不会精确等于 1.0,导致死循环(或者因溢出而终止)
// ✅ 正确:使用整数控制循环次数
for (int i = 0; i < 10; i++) {
double d = i * 0.1;
System.out.println(d);
}
面试考点
Q1:for 循环的三个表达式分别可以省略吗?各自有什么效果?
三个表达式都可以省略,但它们之间必须保留分号
;。
- 省略初始化:使用循环前已声明的变量:
int i=0; for (; i<10; i++)- 省略条件:默认为
true,成为无限循环:for (int i=0; ; i++)(等价于while(true))- 省略迭代:需在循环体内手动更新循环变量:
for (int i=0; i<10; ) { i++; }- 全部省略:
for (;;)是经典的无限循环写法
Q2:增强 for 循环在遍历数组和集合时,底层分别做了什么?
- 数组:编译器将其转换为传统 for 循环,使用索引
i和arr.length。- Iterable 集合:编译器将其转换为
Iterator模式:for (Iterator<T> it = coll.iterator(); it.hasNext(); ) { T e = it.next(); }这也是为什么在增强 for 循环中不能删除集合元素——底层使用的是迭代器,直接调用
list.remove()会导致迭代器的modCount检查失败,抛出ConcurrentModificationException。
Q3:for 循环和 foreach(增强 for)如何选择?
- 需要索引、反向遍历或修改数组元素时:使用传统 for。
- 单纯遍历读取所有元素时:使用增强 for,代码更简洁。
- 遍历集合时:如果不需要删除/修改元素,优先用增强 for。
Q4:嵌套循环中,如何提高性能?
- 将循环不变量的计算提到外层:
for (int i=0; i<list.size(); i++)中如果size()不变,应提前存为局部变量。- 较小的循环放外层,减少内层循环的初始化次数(虽然 JIT 可能优化)。
- 使用
System.arraycopy()代替嵌套循环进行数组拷贝。- 考虑使用位运算或其他算法替代 O(n²) 的嵌套遍历。
小结
| 要点 | 说明 |
|---|---|
| 三要素集中声明 | for(初始化; 条件; 迭代) 结构紧凑,意图清晰 |
| 循环变量作用域 | 在初始化中声明的变量仅限循环内部使用 |
| 嵌套循环 O(m×n) | 外层每执行一次,内层完整执行一轮 |
| 增强 for 最简洁 | 适合单纯遍历,但不支持索引和修改 |
| for 循环中 continue 后迭代语句仍执行 | 与 while 循环的重要区别 |
| 避免浮点循环变量 | 精度误差可能导致循环次数不可控 |
| 警惕分号陷阱 | for(...) ; 中分号成为空循环体 |