while 与 do-while
本教程共 100 篇 · 第 21 篇 · 更新于 2026-08-05 · 约 6 分钟阅读
21. while 与 do-while
本节目标:学会用 while 在「条件成立时反复做事」,分清 while 和 do-while 谁会先执行一次,并掌握防止死循环的基本功。
写程序经常遇到「重复做同一件事」。比如让机器人报数从 1 到 10,或者从用户那里反复读输入直到他输入「退出」。这种「只要条件成立就一直做」的需求,用 while 循环最合适。
循环解决的核心问题是:把「做很多次」这件事交给机器,而不是你手写一百行重复代码。理解循环,是告别「复制粘贴式编程」的第一步。
不用循环会怎样
先看没有循环的日子。打印 1 到 10,你只能这么写:
System.out.println(1);
System.out.println(2);
System.out.println(3);
// ……一直写到 10
```bash
十行还能忍。要打到 1000 呢?要打到「用户输入的那个数」呢?次数在运行前根本不知道,你连行数都写不出来。
循环把这件事变成三个要素:从哪开始、什么时候停、每轮怎么变。三样定下来,跑 10 次和跑 100 万次的代码长度一模一样。
## while:先判断,再执行
while 的结构很简单:先写一个条件,条件为 true 就执行花括号里的代码,执行完再回来看条件,还成立就再来一遍。
```java
public class WhileDemo {
public static void main(String[] args) {
int i = 1; // 计数器,从 1 开始
while (i <= 10) { // 条件成立才进入循环体
System.out.println(i);
i++; // 别忘了让 i 变大,否则永远满足条件
}
}
}
跑起来会依次打印 1 到 10。关键点是:循环体里必须有能改变条件的语句(这里是 i++)。少了它,i 永远是 1,条件永远成立,循环就停不下来。
Warning漏写 i++ 这类「改变条件」的语句,会造出死循环。程序卡死不出结果,严重时 CPU 拉满。写 while 第一反应就问自己:条件靠什么变成 false?
拆解一次循环的过程
拿上面的例子走一遍,脑子里要有这条时间线:
- i = 1,判断 1 <= 10,成立,进循环体,打印 1,i 变成 2。
- 回到条件,2 <= 10 成立,打印 2,i 变成 3。
- ……如此反复。
- 当 i = 11 时,11 <= 10 不成立,循环结束。
把「判断→执行→回头再判断」想成一条传送带,每次经过都检查一次门槛,这就是 while 的灵魂。
注意最后一步:循环结束时 i 的值是 11,不是 10。很多初学者以为循环跑完 i 还停在 10,结果后续逻辑全错。记住条件是「判断失败才退出」,退出时那个变量已经越过边界一步了。
条件必须是 boolean
Java 对循环条件卡得很死:括号里只能放 boolean 表达式。写成赋值会直接编译不过。
int i = 0;
while (i = 10) { // 错:这是赋值,结果是 int,不是 boolean
System.out.println(i);
}
```java
编译器给的提示很明确:
```text
WhileDemo.java:4: error: incompatible types: int cannot be converted to boolean
while (i = 10) {
^
incompatible types 加 int cannot be converted to boolean,说明括号里那个东西算出来是整数,不是真假。检查是不是把 == 写成了 =。
C 语言里这种写法能编译过,跑起来是个死循环。Java 在编译期就把它拦下了,这是语言设计上的一处贴心。
死循环防范
有时候确实需要「一直跑」,比如服务器监听请求。这时可以写 while (true),但循环内部必须有 break 或 return 来跳出,否则永远不结束。
import java.util.Scanner;
public class InputLoop {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while (true) { // 一直循环
System.out.print("说点什么(输入 exit 退出):");
String line = sc.nextLine();
if ("exit".equals(line)) {
break; // 满足条件就跳出
}
System.out.println("你说了:" + line);
}
sc.close();
}
}
```bash
注意这里用 `"exit".equals(line)` 而不是 `line.equals("exit")`。前者即使 line 是 null 也不会抛异常,是更稳妥的写法。
> [!TIP]
> 用 `while (true)` 配合 `break` 是常见写法。只要保证某条分支一定会被触发,就不是真的死循环,而是「受控的无限循环」。
### 真的卡死了怎么办
写循环踩死循环几乎是必经之路。程序不出结果、光标一直闪,别慌:
- 命令行里按 **Ctrl+C** 强制终止。
- IDE 里点控制台旁边的红色停止按钮。
- 如果循环里有 `println`,屏幕会疯狂刷屏,这反而是好事——说明它在哪一轮打转一目了然。
排查时把循环体第一行加一句打印,看变量到底有没有在动:
```java
while (i <= 10) {
System.out.println("当前 i = " + i); // 临时探针,查完删掉
// ……
}
变量一直不变,就是漏了步进语句;变量在变但越跑越远,就是步进方向写反了(比如条件是 i <= 10 却写了 i--)。
别用空循环等时间
老代码里偶尔能看到这种「忙等待」:
// 历史兼容,不推荐:空转烧 CPU
long end = System.currentTimeMillis() + 1000;
while (System.currentTimeMillis() < end) {
// 什么都不做,纯粹空转
}
```java
它确实能等够一秒,但这一秒里 CPU 一个核被吃满。要暂停就用现成的:
```java
try {
Thread.sleep(1000); // 让当前线程歇 1 秒,不占 CPU
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 恢复中断标记
}
Thread.sleep 让线程真正睡过去,CPU 腾给别人用。看到空转 while,基本都能换成 sleep 或后面章节讲的等待机制。
do-while:先执行,再判断
while 是先看条件再干活,条件一开始就不成立,循环体一次都不跑。有时我们想「至少先做一次,再决定要不要继续」,那就用 do-while。
import java.util.Scanner;
public class DoWhileDemo {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int num;
do {
System.out.print("请输入一个正数:");
num = sc.nextInt();
} while (num <= 0); // 输入不对就再来一次
System.out.println("你输入了:" + num);
sc.close();
}
}
```bash
即使第一次就输对了正数,do-while 也会先把循环体执行一遍,再判断条件。这就是它和 while 最本质的区别。
> [!WARNING]
> do-while 的结尾那个**分号不能省**。`} while (num <= 0)` 后面漏了分号,编译器会报 `error: ';' expected`。这是 do-while 独有的语法要求——while 循环的右花括号后面反而不能加分号,否则等于给循环体后面塞了一条空语句。
## 一个对照例子看清差别
```java
int x = 0;
while (x > 0) { // 条件一开始就假
System.out.println("while");
}
// 上面这个循环体一次都不执行
int y = 0;
do {
System.out.println("do-while");
} while (y > 0); // 条件也假,但已经先跑过一次
// 这里会打印一次 "do-while"
同样的条件 > 0 且初始为 0,while 一声不吭地走了,do-while 至少留了个脚印。
两者速查对照
| 对比项 | while | do-while |
|---|---|---|
| 判断时机 | 进循环体之前 | 跑完循环体之后 |
| 最少执行次数 | 0 次 | 1 次 |
| 结尾分号 | 不要 | 必须有 |
| 典型场景 | 条件不满足就别做 | 先做一次再看要不要重来 |
| 使用频率 | 高 | 低 |
该用哪个
- 循环次数不明、可能一次都不做:用 while。
- 至少要执行一次(如「先展示菜单再判断是否继续」):用 do-while。
实际开发里 while 比 do-while 常见得多。do-while 用到的场景相对少,记住「先执行一次」这个特性即可。
一个判断诀窍:把需求念一遍。念出来是「只要……就一直……」,用 while;念出来是「先……然后如果……就再来一次」,用 do-while。
用 while 做累加
while 不只用来打印,更常见的是「边循环边累计」。比如算 1 加到 100:
public class SumWhile {
public static void main(String[] args) {
int i = 1;
int sum = 0;
while (i <= 100) {
sum += i; // 把当前的 i 加进总和
i++;
}
System.out.println("1到100的和:" + sum); // 5050
}
}
```bash
这里有两个变量分工明确:i 负责「走到哪了」,sum 负责「攒了多少」。别把两个职责混在一个变量上,那是新手常犯的糊涂。
注意 `sum` 和 `i` 都声明在循环**外面**。声明在循环体里会怎样?每轮都重新创建、重新赋 0,循环结束后外面还根本访问不到它——变量作用域只到它所在的那对花括号为止。
## 新手易错清单
- 忘写改变条件的语句,循环永不结束。
- 把 `i++` 写成 `i = i + 2` 却没改条件,导致跳过某些值或越界。
- 用 `while (i = 10)` 这种赋值当条件(Java 里条件必须是 boolean,编译直接报错,但写成别的语言容易踩)。
- 在循环里修改了用来判断的变量,却没想清楚它对退出时机的影响。
- while 后面误加分号:`while (i <= 10);` 会把空语句当循环体,后面的花括号变成独立代码块,循环体里什么都不做,直接死循环。
- 累加变量声明在循环体内,每轮被重置。
> [!TIP]
> 写循环前先在纸上走一遍:初始值是多少?每轮结束变成多少?什么时候条件变假?这三问能挡掉大部分死循环。
## 一句话总结
while 是「先看门槛再进门」,do-while 是「先进门再决定是否留」。记住改变条件的语句不能丢,需要至少执行一次才选 do-while,其余情况 while 就够了。
> [!NOTE]
> while 和后面要讲的 for 本质一样,都能实现计数循环。for 把「初始化、条件、步进」写在了一行,更适合知道次数的循环;while 更适合「条件满足就继续」这种不定次数的场景。
## 常见疑问
**问:`while (true)` 和 `for (;;)` 有区别吗?**
没有功能区别,编译后是一样的。`for (;;)` 省掉三段全为空,看着更简洁,C 系语言老手偏爱它;`while (true)` 意图更直白,初学阶段推荐用这个。团队里统一一种就行。
**问:循环变量该声明在循环外还是循环内?**
看你还要不要用它。while 循环的计数器必须声明在外面(不然条件里就访问不到),循环结束后它依然可见。这既是它的便利,也是它的负担——变量活得比需要的久,容易被后面的代码误改。要是只在循环里用,第 22 章的 for 循环能把变量关在循环内部,更干净。
**问:能不能在 while 条件里直接写读取输入?**
能,而且很常见,比如 `while (sc.hasNextInt())`。这类条件每轮都会重新求值,输入没了它自然变 false,循环就退出了。但要留意条件里的方法有没有副作用——`while (sc.nextInt() > 0)` 每判断一次就真的消耗掉一个输入,写的时候得心里有数。