循环是「让机器替你重复」的全部秘密,也是初学者 bug 的第一产地:死循环、差一错误(off-by-one)、该用 while 用了 for。这一章把四种循环形态(while、do-while、for、增强 for)的使用场景理清,再讲清楚 break/continue 和那个 Java 特色的带标签跳转。语法本身没有悬念,悬念全在「怎么选、怎么不写错」。
while:先判断,可能一次不执行:
int i = 0;
while (i < 5) {
System.out.println(i);
i++; // 忘了这行就是死循环,经典中的经典
}
do-while:先干活再判断,至少执行一次。场景几乎只有一个:菜单循环(总得先显示一次菜单才谈得上判断退出):
int choice;
do {
showMenu();
choice = sc.nextInt();
} while (choice != 0);
for:初始化、条件、步进三段合一,循环变量局促在括号内(出了循环不可见,比 while 的变量泄漏干净):
for (int i = 0; i < 5; i++) {
System.out.println(i);
}
三者的选择标准一句话:次数已知用 for,次数未知用 while,至少一次用 do-while。深层逻辑是「把循环的可变部分暴露在哪个位置」:for 把全部控制变量集中在头上一行,审一眼就知道边界;while 的循环变量散落各处(初始化在循环外、步进在循环体内),死循环往往就藏在「体内忘了步进」。
数组和集合的遍历,从 JDK 5 起有了专用语法:
int[] arr = {3, 1, 4};
for (int x : arr) { // 读作「对 arr 里每个 int x」
System.out.println(x);
}
它比下标版好两点:不会越界(没有手写的下标边界可以写错)、意图干净(「逐个取元素」而非「操纵下标」)。代价是拿不到下标、不能边遍历边改结构。所以选择规则:只要不需要下标,一律增强 for;需要下标(改元素值、按位置跳)才用传统 for。第 9 章集合类会把它升级成迭代器视角。
break 跳出当前这层循环,continue 跳过本轮剩余部分进入下一轮。嵌套循环里想直接跳出两层,Java 提供带标签的版本:
outer:
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
if (i * j > 20) break outer; // 直接跳出两层
if (j == 3) continue outer; // 直接进入外层下一轮
}
}
它比 C 的 goto 受约束得多(只能跳到循环边界,不能乱跳),但工程上仍然建议慎用:多数「想 break outer」的场景可以提取成方法后 return(更清晰)。标签语法留给「双层搜索命中即全退」这类真实受益的场景。两个易错点顺带钉死:continue 在 for 循环里仍会执行步进(i++ 照走),在 while 里则直接回到条件判断(步进写在循环体尾部的话会被跳过,死循环预警);break 只跳一层,嵌套两层以上时数好层数。
「该用小于还是小于等于」「该从 0 还是从 1」「数组边界在 length 还是 length-1」,这三个问题组成了 off-by-one 家族。两个根治手段:
数一数 iteration 次数而不是盯着端点。for (int i = 0; i < n; i++) 跑 n 次(0 到 n-1),i <= n 跑 n+1 次;问自己「我要几次」,别问「终点是几」。
用「不变式」审循环:每轮开始时循环处理到哪了、结束后边界在哪,写成注释检查。例子(打印 1 到 n 的偶数):
for (int i = 2; i <= n; i += 2) { // 不变式:i 是当前待处理的偶数
System.out.println(i); // n=10 打印 2..10;n=11 也打印到 10,对
}
循环嵌套时的复杂度也要心里有账:两层各 n 次,总操作 n²,数据结构课第 1 章的表(O(n²) 在百万数据上是分钟级)随时会兑现,先算规模再写循环是竞赛和工程共同的纪律。
把本章所有零件拧在一起,顺便演示循环+条件+输入的协作:
import java.util.Scanner;
import java.util.Random;
public class Guess {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int answer = new Random().nextInt(100) + 1; // 1~100
int tries = 0;
while (true) { // 次数未知 → while
System.out.print("猜:");
int g = sc.nextInt();
tries++;
if (g == answer) {
System.out.println("对了,共 " + tries + " 次");
break; // 命中即出
}
System.out.println(g < answer ? "小了" : "大了");
}
}
}
while (true) + break 是「循环主体天然就是主逻辑、退出条件在中间」场景的标准姿势,比把一堆条件挤进 while 括号里可读得多。
i-- 写成 i++。调试手段:在循环体首行打印循环变量,盯它是否在变。== 比较字符串放进循环条件。第 3 章的坑在循环里更常见(while (input != "quit") 永远为真),用 equals。j <= i),再进阶:对齐版用 printf("%2d")。for (int i = 0; i < 10; i++) { if (i % 3 == 0) continue; System.out.println(i); }