第 9 章:流程控制 — 循环结构
学习目标
- 掌握 for / while / do-while 三种循环
- 学会 break、continue、带标签的 break
- 理解 for-each 语法糖
- 避免死循环和常见循环陷阱
一、为什么需要循环?
如果让你打印 1 到 100,你会怎么做?
java
// ❌ 反例:写 100 行
System.out.println(1);
System.out.println(2);
System.out.println(3);
// ... 一直写到 100这就是循环要解决的问题:重复执行某段代码。
二、for 循环
2.1 标准 for 循环
java
for (初始化; 条件; 更新) {
// 循环体
}java
// 打印 1 到 5
for (int i = 1; i <= 5; i++) { // ① i 从 1 开始,每次循环 +1,到 6 结束
System.out.println(i);
}
// 输出:1 2 3 4 5执行流程:
2.2 多种写法
java
// 标准:从 1 到 10
for (int i = 1; i <= 10; i++) { }
// 反向:从 10 到 1
for (int i = 10; i >= 1; i--) { }
// 步长 2:打印偶数
for (int i = 0; i <= 10; i += 2) { }
// 多个变量
for (int i = 0, j = 10; i < j; i++, j--) {
System.out.println("i=" + i + ", j=" + j);
}2.3 for-each(增强 for)
JDK 5 引入的语法糖,专门用于遍历数组和集合:
java
int[] numbers = {1, 2, 3, 4, 5};
// ✅ 传统写法
for (int i = 0; i < numbers.length; i++) {
System.out.println(numbers[i]);
}
// ✅ for-each 写法(推荐)
for (int number : numbers) { // ① ":" 读作 "in"
System.out.println(number);
}for-each 的限制:
java
// ❌ 不能修改原数组
for (int number : numbers) {
number = number * 2; // 这只是修改临时变量,不影响原数组
}
// ❌ 不能访问索引
for (int number : numbers) {
// 不知道当前是第几个
}
// ✅ 需要索引时用传统 for
for (int i = 0; i < numbers.length; i++) {
if (i % 2 == 0) {
System.out.println("索引 " + i + ": " + numbers[i]);
}
}三、while 循环
当循环次数不固定时,用 while 更合适。
java
while (条件) {
// 循环体
}java
// 场景:用户输入直到正确为止
Scanner scanner = new Scanner(System.in);
String input = "";
while (!"exit".equals(input)) { // ① 条件为 true 时持续执行
System.out.print("输入命令(exit 退出): ");
input = scanner.nextLine();
System.out.println("收到: " + input);
}while vs for 选择
java
// 已知次数:用 for
for (int i = 0; i < 100; i++) {
// ...
}
// 未知次数:用 while
while (!condition) {
// ...
}四、do-while 循环
先执行,再判断。至少执行一次。
java
do {
// 循环体
} while (条件); // 注意分号java
// 场景:菜单选择,至少要显示一次
int choice;
do {
System.out.println("1. 开始游戏");
System.out.println("2. 加载存档");
System.out.println("3. 退出");
System.out.print("请选择: ");
choice = scanner.nextInt();
} while (choice < 1 || choice > 3);五、break 和 continue
5.1 break:跳出循环
java
// 找到第一个满足条件的就退出
for (int i = 1; i <= 100; i++) {
if (i % 17 == 0) {
System.out.println("找到第一个 17 的倍数: " + i);
break; // ① 跳出整个 for 循环
}
}5.2 continue:跳过本轮
java
// 跳过偶数,只处理奇数
for (int i = 1; i <= 10; i++) {
if (i % 2 == 0) {
continue; // ① 跳过本轮,进入下一轮
}
System.out.println(i);
}
// 输出:1 3 5 7 95.3 带标签的 break(跳出多层循环)
java
// 场景:在二维数组中查找,找到后跳出所有循环
int[][] matrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
boolean found = false;
outer: // ① 标签
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[i].length; j++) {
if (matrix[i][j] == 5) {
System.out.println("找到 5 在 [" + i + "][" + j + "]");
found = true;
break outer; // ② 跳出 outer 标签标记的循环
}
}
}带标签的 break 谨慎使用
- 可读性差,新人难理解
- 大部分情况可以用方法 + return 替代
- 仅在确实需要跳出多层循环时使用
六、循环的常见陷阱
6.1 死循环
java
// ❌ 死循环 1:忘记更新循环变量
int i = 0;
while (i < 10) {
System.out.println(i);
// 忘记 i++
}
// ❌ 死循环 2:条件永远为真
for (;;) { // 等价于 while (true)
// 没有 break 就会无限执行
}6.2 浮点数循环的精度问题
java
// ❌ 危险:浮点数累加可能导致循环次数错误
for (double d = 0.0; d != 1.0; d += 0.1) {
System.out.println(d);
// 可能少输出 1.0(因为 0.1 不能精确表示)
}
// ✅ 改用整数计数
for (int i = 0; i <= 10; i++) {
double d = i / 10.0;
System.out.println(d);
}6.3 集合遍历时修改
java
List<Integer> list = new ArrayList<>(List.of(1, 2, 3));
// ❌ ConcurrentModificationException
for (Integer num : list) {
if (num == 2) {
list.remove(num); // 遍历时删除会抛异常
}
}
// ✅ 用迭代器
Iterator<Integer> it = list.iterator();
while (it.hasNext()) {
if (it.next() == 2) {
it.remove(); // 迭代器的 remove 是安全的
}
}七、完整代码示例
java
/**
* 循环结构综合示例
*
* 配套文档:第 9 章 流程控制 - 循环结构
* 运行方式:javac LoopDemo.java && java LoopDemo
*/
public class LoopDemo {
public static void main(String[] args) {
// ============ 1. for 循环 ============
System.out.println("=== for ===");
for (int i = 1; i <= 5; i++) {
System.out.print(i + " ");
}
System.out.println();
// ============ 2. for-each 遍历 ============
System.out.println("\n=== for-each ===");
int[] scores = {85, 92, 78, 95, 88};
int total = 0;
for (int score : scores) {
total += score;
}
System.out.println("总分: " + total + ", 平均: " + total / scores.length);
// ============ 3. while 循环(未知次数)============
System.out.println("\n=== while ===");
int sum = 0, n = 1;
while (sum < 100) {
sum += n;
n++;
}
System.out.println("累加到 " + sum + " 用了 " + (n - 1) + " 个数");
// ============ 4. do-while(至少执行一次)============
System.out.println("\n=== do-while ===");
int i = 10;
do {
System.out.println("至少执行一次: i = " + i);
} while (i < 5); // 条件为 false,但仍执行了一次
// ============ 5. break 和 continue ============
System.out.println("\n=== break 和 continue ===");
for (int j = 1; j <= 10; j++) {
if (j == 3) continue; // ① 跳过 3
if (j == 7) break; // ② 到 7 退出
System.out.print(j + " ");
}
System.out.println();
// ============ 6. 带标签的 break ============
System.out.println("\n=== 带标签的 break ===");
int[][] matrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
searchLabel:
for (int row = 0; row < matrix.length; row++) {
for (int col = 0; col < matrix[row].length; col++) {
if (matrix[row][col] == 5) {
System.out.println("找到 5: [" + row + "][" + col + "]");
break searchLabel;
}
}
}
// ============ 7. 嵌套循环:九九乘法表 ============
System.out.println("\n=== 九九乘法表 ===");
for (int a = 1; a <= 9; a++) {
for (int b = 1; b <= a; b++) {
System.out.print(b + "×" + a + "=" + (a * b) + "\t");
}
System.out.println();
}
}
}运行结果:
=== for ===
1 2 3 4 5
=== for-each ===
总分: 438, 平均: 87
=== while ===
累加到 105 用了 14 个数
=== do-while ===
至少执行一次: i = 10
=== break 和 continue ===
1 2 4 5 6
=== 带标签的 break ===
找到 5: [1][1]
=== 九九乘法表 ===
1×1=1
1×2=2 2×2=4
1×3=3 2×3=6 3×3=9
...八、常见面试题
Q1: 三种循环的使用场景?
| 循环 | 适用 |
|---|---|
for | 已知循环次数 |
while | 未知次数,靠条件退出 |
do-while | 至少执行一次(很少用) |
for-each | 遍历数组 / 集合(推荐) |
Q2: for 和 while 性能有差异吗?
现代 JVM 优化后无差异。for 循环的初始化只执行一次,while 也类似。选择依据是可读性,不是性能。
Q3: break 和 continue 的区别?
break:跳出整个循环continue:跳过本轮,进入下一轮
Q4: for(;😉 和 while(true) 一样吗?
完全等价。for(;;) 没有初始 / 条件 / 更新,等价于无限循环。
九、本章小结
| 要点 | 关键 |
|---|---|
| for 循环 | 已知次数,三段式(初始化/条件/更新) |
| for-each | 遍历数组 / 集合的首选,但不能改原集合 |
| while | 未知次数,靠条件退出 |
| do-while | 至少执行一次 |
| break | 跳出整个循环 |
| continue | 跳过本轮,进入下一轮 |
| 带标签的 break | 跳出多层循环,谨慎使用 |
动手练习
练习 1:基础题
打印 1 到 100 中所有 3 的倍数:
java
for (int i = 1; i <= 100; i++) {
// 你的代码
}
// 期望输出:3 6 9 12 ... 99练习 2:进阶题
打印斐波那契数列前 20 项:
1 1 2 3 5 8 13 21 34 ...练习 3:挑战题
找 100 以内的所有素数(只能被 1 和自己整除):
java
// 提示:外层循环 2~100,内层循环判断能否被整除
// 期望输出:2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 ...推荐阅读
- 📖 《Java 核心技术》卷 1 第 3.6.2 节 — 循环详解
- 🌐 JEP 363: Switch Expressions — switch 表达式
下一章:第 10 章:方法定义、调用与重载 →