while 循环
"有些时候,你不知道循环会执行多少次——就像一个用户在登录界面反复输入密码,你无法预知他第几次才能输对。" —— 朱璐在实现飞翔云笔记登录模块时对实习生小崔解释,"这种'先判断再执行'的场景,while 循环是最自然的选择。"
概述
while 循环是"先判断,后执行"的循环结构。它会在每次循环体执行前检查条件——条件为 true 才进入循环体,为 false 则直接跳过。适合循环次数不确定但有明确终止条件的场景。
while 与 for 的定位
| 场景 | 推荐结构 | 原因 |
|---|---|---|
| 循环次数已知(如遍历数组) | for | 三要素集中,结构紧凑 |
| 循环次数未知(如等待用户输入) | while | 语义更清晰,"当...时就继续" |
| 至少执行一次(如菜单交互) | do-while | 保证首次执行 |
语法格式
while (布尔表达式) {
// 循环体
}
关键点表格
| 关键点 | 说明 |
|---|---|
| 条件判断 | 每次循环开始前判断,true 则执行循环体,false 则退出 |
| 循环体可能 0 次 | 如果首次判断条件即为 false,循环体一次也不执行 |
| 循环三要素 | 初始化在 while 之前,条件在 while 括号内,迭代在循环体中 |
| 死循环 | while (true) { ... } 是合法的无限循环 |
执行流程
对比 for 循环:for 将三个要素集中在括号内;while 将初始化放在循环前、迭代放在循环体中。本质完全相同,只是写法不同。
示例一:用户登录重试
Frank 为飞翔云笔记设计了登录重试机制——用户最多可以尝试 3 次输入密码:
/**
* 飞翔云笔记登录——while 循环(重试限制)
*/
import java.util.Scanner;
public class LoginRetry {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
final String CORRECT_PASSWORD = "feixiang2024";
final int MAX_RETRIES = 3;
int attempts = 0; // ① 初始化:已尝试次数
System.out.println("===== 飞翔云笔记登录 =====");
// ② 条件:未超过最大次数 且 未登录成功
while (attempts < MAX_RETRIES) {
System.out.print("请输入密码(剩余 "
+ (MAX_RETRIES - attempts) + " 次机会):");
String input = scanner.nextLine();
if (CORRECT_PASSWORD.equals(input)) {
System.out.println("登录成功!欢迎使用飞翔云笔记。");
break; // 登录成功,跳出循环
}
attempts++; // ③ 迭代:尝试次数 +1
if (attempts < MAX_RETRIES) {
System.out.println("密码错误,请重试。");
}
}
if (attempts >= MAX_RETRIES) {
System.out.println("已连续3次输入错误,账户已锁定。");
System.out.println("请联系管理员:admin@feixiang.net");
}
scanner.close();
System.out.println("登录流程结束。");
}
}
运行输出(第3次成功):
===== 飞翔云笔记登录 =====
请输入密码(剩余 3 次机会):123456
密码错误,请重试。
请输入密码(剩余 2 次机会):password
密码错误,请重试。
请输入密码(剩余 1 次机会):feixiang2024
登录成功!欢迎使用飞翔云笔记。
登录流程结束。
运行输出(全部失败):
===== 飞翔云笔记登录 =====
请输入密码(剩余 3 次机会):111
密码错误,请重试。
请输入密码(剩余 2 次机会):222
密码错误,请重试。
请输入密码(剩余 1 次机会):333
已连续3次输入错误,账户已锁定。
请联系管理员:admin@feixiang.net
登录流程结束。
示例二:工资条批量生成
大翔需要按员工编号范围批量生成工资条。while 循环从起始编号逐个处理到结束编号:
/**
* 飞翔科技工资条批量生成——while 循环(固定范围遍历)
*/
public class PayrollGeneration {
public static void main(String[] args) {
int startId = 1001; // 起始员工编号
int endId = 1008; // 结束员工编号
int currentId = startId; // ① 初始化
String[] names = {"大翔", "白歌", "小崔", "朱璐",
"Frank", "李明", "王芳", "赵磊"};
int[] salaries = {60000, 45000, 25000, 22000,
30000, 18000, 16000, 20000};
int index = 0;
int totalPayroll = 0; // 总薪酬支出
System.out.println("===== 飞翔科技工资条 =====");
System.out.println("月份:2024年6月");
System.out.println("------------------------------");
while (currentId <= endId) { // ② 条件
String name = names[index];
int salary = salaries[index];
double tax = salary * 0.1; // 简单按10%计税
double netPay = salary - tax;
System.out.printf("编号:%d | 姓名:%s | 应发:¥%d | "
+ "个税:¥%.0f | 实发:¥%.0f\n",
currentId, name, salary, tax, netPay);
totalPayroll += salary;
currentId++; // ③ 迭代:编号 +1
index++; // ③ 迭代:数组索引 +1
}
System.out.println("------------------------------");
System.out.println("本月薪酬总额:¥" + totalPayroll);
System.out.println("工资条全部生成完毕。");
}
}
运行输出:
===== 飞翔科技工资条 =====
月份:2024年6月
------------------------------
编号:1001 | 姓名:大翔 | 应发:¥60000 | 个税:¥6000 | 实发:¥54000
编号:1002 | 姓名:白歌 | 应发:¥45000 | 个税:¥4500 | 实发:¥40500
编号:1003 | 姓名:小崔 | 应发:¥25000 | 个税:¥2500 | 实发:¥22500
编号:1004 | 姓名:朱璐 | 应发:¥22000 | 个税:¥2200 | 实发:¥19800
编号:1005 | 姓名:Frank | 应发:¥30000 | 个税:¥3000 | 实发:¥27000
编号:1006 | 姓名:李明 | 应发:¥18000 | 个税:¥1800 | 实发:¥16200
编号:1007 | 姓名:王芳 | 应发:¥16000 | 个税:¥1600 | 实发:¥14400
编号:1008 | 姓名:赵磊 | 应发:¥20000 | 个税:¥2000 | 实发:¥18000
------------------------------
本月薪酬总额:¥236000
工资条全部生成完毕。
while 与 for 的本质等价
任何 while 循环都可以转换为 for 循环,反之亦然:
// while 版本
int i = 0; // 初始化
while (i < 10) { // 条件
System.out.println(i);
i++; // 迭代
}
// 等价 for 版本
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
选择建议:当你思考问题时自然使用"当...时,重复做..."的语言,就用 while;当自然使用"从...到...,每次..."的语言,就用 for。
深度原理:while 循环的字节码实现
Java 源码:
int i = 0;
while (i < 10) {
System.out.println(i);
i++;
}
反编译字节码(javap -c):
0: iconst_0 // i = 0
1: istore_1
2: iload_1 // 加载 i
3: bipush 10 // 加载常量 10
5: if_icmpge 21 // 如果 i >= 10,跳转到偏移量 21(循环结束)
8: getstatic System.out // 准备 println
11: iload_1 // 加载 i
12: invokevirtual println // println(i)
15: iinc 1, 1 // i++
18: goto 2 // 跳回偏移量 2(条件判断)
21: return
与 for 循环字节码对比:while 和 for 在字节码层面几乎完全相同,只有两点差异:
- while 的初始化指令在循环判断之前(无额外标记)
- for 的迭代指令在
goto之前,而 while 的迭代在循环体中由开发者显式编写
JIT 编译器进一步优化后,两者的性能完全相同。
易错场景
反例一:忘记迭代导致死循环
小崔写了一个打印员工列表的 while 循环,却忘记让索引递增:
// ❌ 错误:缺少迭代语句 → 死循环
String[] employees = {"大翔", "白歌", "小崔"};
int i = 0;
while (i < employees.length) {
System.out.println(employees[i]);
// 忘记 i++ → i 永远为 0,条件永远为 true → 死循环!
}
运行输出:
大翔
大翔
大翔
...(无限循环,直到强制终止)
// ✅ 正确:务必在循环体中包含迭代逻辑
String[] employees = {"大翔", "白歌", "小崔"};
int i = 0;
while (i < employees.length) {
System.out.println(employees[i]);
i++; // 必须!
}
反例二:循环体可能不执行
// while 循环体可能一次都不执行
int count = 0;
while (count > 0) { // 首次判断即为 false
System.out.println("这行永远不会打印");
}
// 没有任何输出
如果需要"至少执行一次",请使用 do-while。
反例三:条件永远为 true 的死循环
// ❌ 错误:条件永远为 true(除非 break)
while (true) {
System.out.println("服务器运行中...");
// 缺少退出条件
}
// 程序永远不会正常终止
// ✅ 正确:设置明确的退出条件
boolean running = true;
int tickCount = 0;
while (running) {
System.out.println("服务器运行中...第 " + (++tickCount) + " 秒");
if (tickCount >= 5) {
System.out.println("收到关闭信号,服务器即将停止。");
running = false; // 退出循环
}
}
运行输出:
服务器运行中...第 1 秒
服务器运行中...第 2 秒
服务器运行中...第 3 秒
服务器运行中...第 4 秒
服务器运行中...第 5 秒
收到关闭信号,服务器即将停止。
反例四:while 中的 continue 陷阱
// ❌ 错误:continue 跳过了迭代语句
int i = 0;
while (i < 5) {
if (i == 2) {
continue; // 跳回条件判断,但 i 没递增 → 死循环!
}
System.out.println(i);
i++;
}
运行输出:
0
1
...(之后无限循环,因为 i 始终为 2)
这是 while 和 for 的重要区别:
// for 循环中 continue 后迭代语句仍会执行
for (int i = 0; i < 5; i++) {
if (i == 2) {
continue; // i++ 仍然执行,i 变为 3
}
System.out.println(i); // 输出 0, 1, 3, 4
}
// ✅ while 中正确使用 continue
int i = 0;
while (i < 5) {
if (i == 2) {
i++; // 必须在 continue 前手动递增
continue;
}
System.out.println(i);
i++;
}
面试考点
Q1:while 循环和 for 循环在什么情况下可以互换?什么情况下不可以?
可以互换:几乎所有循环都可以互换。
推荐场景:
- 遍历数组/集合(循环次数已知)→ for
- 等待某个条件满足(循环次数未知)→ while
- 至少执行一次 → do-while
代码风格:选择最清晰表达意图的结构,而不是死板地遵循规则。
Q2:while(true) 和 for(;😉 有什么区别?
语义上无区别,两者都产生无限循环。字节码层面:
while(true):编译器生成goto回路for(;;):省略条件表达式,编译器同样生成无条件跳转性能完全相同。选择哪个是代码风格问题,
while(true)更直观,for(;;)更紧凑。
Q3:while 循环中如何安全使用 continue?
while 循环中,
continue会跳过当前迭代并直接回到条件判断。因此必须确保迭代逻辑在 continue 之前执行,否则会造成死循环。推荐做法:将迭代操作放在循环体的最前面(或使用 for 循环避免此问题)。
Q4:while 循环的条件表达式可以写赋值语句吗?
Java 不允许——条件表达式必须返回 boolean 类型,而赋值语句返回的是被赋的值(如
(i = 5)返回 int 类型 5)。编译器会报错:int cannot be converted to boolean。这与 C 语言不同,C 语言中
while (i = 5)是合法的(且极其容易引发 Bug)。Java 的类型安全设计再次保护了你。
小结
| 要点 | 说明 |
|---|---|
| 先判断,后执行 | while 可能在 0 次执行后即退出 |
| 三要素分散 | 初始化在循环前,条件在括号内,迭代在循环体中 |
| 与 for 本质等价 | 任何 while 都可改写为 for,选择标准是语义清晰度 |
| 警惕缺少迭代 | 循环体中必须有让条件趋向 false 的逻辑 |
| while 中的 continue 陷阱 | continue 跳过迭代语句可能导致死循环 |
| 字节码与 for 几乎一致 | 主要区别仅在于初始化指令的放置位置 |