do-while 循环
"菜单程序必须至少显示一次——用户还没看到菜单呢,你怎么能先问他'还要继续吗'?" —— Frank 在给飞翔云笔记设计命令行管理工具时,对实习生小崔解释了 do-while 的使用场景。
概述
do-while 循环是三种循环中唯一保证至少执行一次的循环结构。它先执行循环体,再判断条件——即使条件首次即为 false,循环体也已经执行了一轮。
三种循环执行次数对比
| 循环结构 | 最少执行次数 | 判断时机 |
|---|---|---|
| for | 0 次 | 先判断 |
| while | 0 次 | 先判断 |
| do-while | 1 次 | 后判断 |
语法格式
do {
// 循环体(至少执行一次)
} while (布尔表达式); // ← 注意这里的分号!
关键点表格
| 关键点 | 说明 |
|---|---|
| 保证至少一次 | 循环体在判断条件前就已执行 |
| 末尾分号 | while(条件); 末尾的分号不可省略 |
| 适合交互场景 | 菜单、用户输入校验等"先做一次再决定是否继续"的场景 |
| 循环三要素 | 初始化在 do 之前,条件在 while 括号内,迭代在循环体中 |
执行流程
与 while 的对比:while 是"判断→执行→判断→...",do-while 是"执行→判断→执行→..."。仅此顺序差异,带来了"至少一次"的保证。
示例一:命令行菜单交互
小崔为飞翔科技内部工具开发了一个命令行菜单,用户可以反复选择操作直到主动退出:
/**
* 飞翔科技内部工具菜单——do-while 循环
*/
import java.util.Scanner;
public class CommandMenu {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int choice; // ① 初始化:在 do 之前声明
System.out.println("===== 飞翔科技内部工具 v1.0 =====");
do {
// ② 循环体:显示菜单并获取用户选择
System.out.println();
System.out.println("请选择操作:");
System.out.println(" 1. 查看员工列表");
System.out.println(" 2. 添加工资记录");
System.out.println(" 3. 生成报表");
System.out.println(" 4. 系统设置");
System.out.println(" 0. 退出系统");
System.out.print("请输入选项(0-4):");
choice = scanner.nextInt(); // ③ 迭代:更新 choice
scanner.nextLine(); // 消费换行符
switch (choice) {
case 1:
System.out.println(">> 正在加载员工列表...");
System.out.println(">> 大翔 | 白歌 | 小崔 | 朱璐 | Frank");
break;
case 2:
System.out.println(">> 工资记录已添加。");
break;
case 3:
System.out.println(">> 正在生成月度报表...");
System.out.println(">> 报表已发送至 admin@feixiang.net");
break;
case 4:
System.out.println(">> 进入系统设置...");
break;
case 0:
System.out.println(">> 正在退出系统,感谢使用!");
break;
default:
System.out.println(">> 无效选项,请重新输入(0-4)。");
break;
}
} while (choice != 0); // ③ 条件:choice 不为 0 时继续循环
scanner.close();
System.out.println("系统已退出。");
}
}
运行输出:
===== 飞翔科技内部工具 v1.0 =====
请选择操作:
1. 查看员工列表
2. 添加工资记录
3. 生成报表
4. 系统设置
0. 退出系统
请输入选项(0-4):1
>> 正在加载员工列表...
>> 大翔 | 白歌 | 小崔 | 朱璐 | Frank
请选择操作:
1. 查看员工列表
2. 添加工资记录
3. 生成报表
4. 系统设置
0. 退出系统
请输入选项(0-4):3
>> 正在生成月度报表...
>> 报表已发送至 admin@feixiang.net
请选择操作:
1. 查看员工列表
2. 添加工资记录
3. 生成报表
4. 系统设置
0. 退出系统
请输入选项(0-4):0
>> 正在退出系统,感谢使用!
系统已退出。
示例二:输入数据校验
白歌为员工信息录入系统设计了输入校验——工号必须是 1000~9999 之间的整数,如果用户输入非法值则提示重新输入:
/**
* 飞翔科技员工信息录入——do-while 输入校验
*/
import java.util.Scanner;
public class EmployeeInputValidation {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String name;
int employeeId;
System.out.println("===== 飞翔科技员工信息录入 =====");
// do-while 确保至少提示一次输入
do {
System.out.print("请输入员工姓名(不能为空):");
name = scanner.nextLine().trim();
if (name.isEmpty()) {
System.out.println("【错误】姓名不能为空,请重新输入。");
}
} while (name.isEmpty());
// do-while 校验工号范围
do {
System.out.print("请输入工号(1000-9999):");
employeeId = scanner.nextInt();
scanner.nextLine(); // 消费换行符
if (employeeId < 1000 || employeeId > 9999) {
System.out.println("【错误】工号必须在 1000~9999 之间,"
+ "你输入的是 " + employeeId);
}
} while (employeeId < 1000 || employeeId > 9999);
System.out.println();
System.out.println("--- 录入信息确认 ---");
System.out.println("姓名:" + name);
System.out.println("工号:" + employeeId);
System.out.println("信息录入成功!");
scanner.close();
}
}
运行输出:
===== 飞翔科技员工信息录入 =====
请输入员工姓名(不能为空):
【错误】姓名不能为空,请重新输入。
请输入员工姓名(不能为空):小崔
请输入工号(1000-9999):99
【错误】工号必须在 1000~9999 之间,你输入的是 99
请输入工号(1000-9999):10000
【错误】工号必须在 1000~9999 之间,你输入的是 10000
请输入工号(1000-9999):1003
--- 录入信息确认 ---
姓名:小崔
工号:1003
信息录入成功!
do-while 与 while 的本质区别
用一个极简例子说明两者的差异:
// while:首先判断 i < 0,结果为 false,循环体一次都不执行
int i = 0;
while (i < 0) {
System.out.println("while 循环体"); // 不会执行
}
// do-while:先执行循环体,然后判断 i < 0,结果为 false,退出
int j = 0;
do {
System.out.println("do-while 循环体"); // 会执行一次!
} while (j < 0);
运行输出:
do-while 循环体
深度原理:do-while 循环的字节码实现
Java 源码:
int i = 0;
do {
System.out.println(i);
i++;
} while (i < 3);
反编译字节码(javap -c):
0: iconst_0 // i = 0
1: istore_1
2: getstatic System.out // ← 循环体起点:直接开始执行
5: iload_1
6: invokevirtual println // println(i)
9: iinc 1, 1 // i++
12: iload_1 // 加载 i
13: iconst_3 // 加载常量 3
14: if_icmplt 2 // 如果 i < 3,跳回偏移量 2(循环体起点)
17: return
与 while 字节码对比:
| 结构 | 条件判断位置 | 首次执行前是否有判断 |
|---|---|---|
| while | 循环体之前 | 有(if_icmpge 开头就判断) |
| do-while | 循环体之后 | 无(直接进入循环体,最后才判断) |
do-while 的字节码结构更紧凑——少了一次无条件
goto跳转。但这点微小的性能差异在现代 JIT 编译器面前完全可以忽略。
易错场景
反例一:忘记末尾的分号
// ❌ 编译错误:do-while 末尾缺少分号
int i = 0;
do {
System.out.println(i);
i++;
} while (i < 3) // 缺少分号!编译失败
// ✅ 正确:必须加分号
int i = 0;
do {
System.out.println(i);
i++;
} while (i < 3); // ← 分号!
为什么需要分号? do { ... } while (条件); 是一个完整的语句,分号是语句结束的标志。与 while (条件) { ... } 不同,后者以 } 结尾,不需要额外分号。
反例二:误用 do-while 替代 while
// ❌ 错误:应该用 while,却用了 do-while
String[] names = {};
int i = 0;
do {
System.out.println(names[i]); // ArrayIndexOutOfBoundsException!
i++;
} while (i < names.length);
// names 为空数组时,do 体仍然执行,导致数组越界
// ✅ 正确:空数组场景应该用 while(先判断)
String[] names = {};
int i = 0;
while (i < names.length) {
System.out.println(names[i]);
i++;
}
// 条件首次判断即为 false,循环体不执行,安全
反例三:while 条件中使用了未在 do 体内更新的变量
// ❌ 错误:条件中的变量在循环体内未更新 → 死循环
int count = 0;
int target = 5;
do {
System.out.println("处理中...");
// 忘记更新 count → 永远不会达到 target
} while (count < target);
// ✅ 正确:在循环体内更新条件变量
int count = 0;
int target = 5;
do {
System.out.println("处理第 " + (count + 1) + " 次...");
count++; // 必须更新!
} while (count < target);
反例四:循环变量作用域问题
// ❌ 错误:在 do 体内声明的变量,while 条件无法访问
do {
int x = 10;
// ...
} while (x > 0); // 编译错误:cannot find symbol x
// ✅ 正确:在 do 之前声明变量
int x;
do {
x = 10;
// ...
} while (x > 0);
面试考点
Q1:do-while 和 while 的本质区别是什么?各适用于什么场景?
本质区别:判断时机不同——while 先判断后执行,do-while 先执行后判断。
适用场景:
- do-while:需要确保循环体至少执行一次。如菜单交互(至少显示一次菜单)、输入校验(至少提示一次输入)、游戏主循环(至少渲染一帧)。
- while:循环体可能不需要执行。如遍历集合(可能为空)、等待事件发生(事件可能已发生)、处理数据(可能没有数据)。
字节码层面:while 在循环体前有判断跳转指令;do-while 在循环体后才有判断,代码更紧凑。
Q2:为什么 do-while 在实际代码中使用频率远低于 for 和 while?
- "至少执行一次"的场景相对少:大多数循环场景(遍历、搜索、等待)都需要"可能不执行"的语义。
- 可读性:
while的条件放在开头,阅读代码时先看到退出条件;do-while的条件在末尾,需要读完整个循环体才知道退出条件。- for 更紧凑:已知次数的遍历几乎全用 for。
但这并不意味着 do-while 不重要——在正确的场景使用它,代码会更自然。
Q3:如何将 do-while 循环改写为 while 循环?代价是什么?
// do-while 版本 do { 循环体 } while (条件); // while 等价版本(需要重复循环体代码) 循环体; // 先执行一次 while (条件) { 循环体; }代价:代码重复(DRY 原则违反)。如果循环体较长或需要修改,两个地方都要同步更新,容易出错。这就是 do-while 存在的价值——消除重复。
Q4:do-while(false) 有什么实际用途?
do { ... } while(false);是一种代码块控制技巧,并非真正的循环:do { if (condition1) break; if (condition2) break; // 正常执行逻辑 } while (false);它模拟了"提前退出"的模式,类似于
goto的安全替代。但在现代 Java 中,更好的方式是将逻辑抽取为独立方法并使用return。
小结
| 要点 | 说明 |
|---|---|
| 至少执行一次 | do-while 是唯一保证循环体至少执行一次的循环结构 |
| 末尾分号不可省 | while(条件); 的分号是语句结束标志 |
| 先执行,后判断 | 循环体完成后才判断是否继续 |
| 适合交互场景 | 菜单、输入校验、游戏主循环等需要先展示/响应的场景 |
| 字节码更紧凑 | 相比 while 少一次无条件跳转(实际性能差异可忽略) |
| 避免空集合越界 | 遍历可能为空的集合时应使用 while 而非 do-while |