首页 / Java 入门教程 / while 与 do-while

Java 入门教程

while 与 do-while

本教程共 100 篇 · 第 21 篇 · 更新于 2026-08-05 · 约 6 分钟阅读

JavaJava 入门教程while循环do-while死循环

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?

拆解一次循环的过程

拿上面的例子走一遍,脑子里要有这条时间线:

  1. i = 1,判断 1 <= 10,成立,进循环体,打印 1,i 变成 2。
  2. 回到条件,2 <= 10 成立,打印 2,i 变成 3。
  3. ……如此反复。
  4. 当 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 typesint cannot be converted to boolean,说明括号里那个东西算出来是整数,不是真假。检查是不是把 == 写成了 =

C 语言里这种写法能编译过,跑起来是个死循环。Java 在编译期就把它拦下了,这是语言设计上的一处贴心。

死循环防范

有时候确实需要「一直跑」,比如服务器监听请求。这时可以写 while (true),但循环内部必须有 breakreturn 来跳出,否则永远不结束。

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 至少留了个脚印。

两者速查对照

对比项whiledo-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)` 每判断一次就真的消耗掉一个输入,写的时候得心里有数。