# 循环：让同一段代码“再来一遍”

while 与 for 的写法、执行顺序与选型：计数器从 0 还是 1 起步，以及死循环、差一错误、条件后多余分号三个新手坑

> 控制流 · 循环 · 变量与类型 · 约 7 分钟 · 09 月 07 日

## 本篇要点

1. while 和 for 都靠同一个 boolean 条件驱动：条件为 true 就执行整个循环体，执行完再回头检查条件，直到某次为 false 才跳出；条件一开始就是 false 时循环体一次也不执行。
2. for 的头部三段各司其职：初始化只执行一次，终止条件在每轮开始前检查，更新在每轮循环体执行完之后才运行——它的执行顺序是“初始化 → 检查 → 循环体 → 更新 → 再检查”的循环。
3. 计数器起步没有对错之分：想数“1 到 n”写 i = 1 配 i <= n；想“恰好做 n 轮”写 i = 0 配 i < n，后者是遍历数组的惯用式，也是差一错误最常出现的地方。
4. 选 while 还是 for：次数事先数得清就用 for（如从 1 加到 100），次数取决于运行中发生什么就用 while（如不断翻倍直到超过 100）。
5. 三个经典坑都源于循环三要素（准备、条件、让条件变假的变化）缺位：忘记更新导致死循环，<= 与 < 用错导致差一错误，条件后误加分号让空语句窃取循环体——分号那一行是语法合法却行为全错的典型。
6. 一个健康的循环必然具备三要素，末尾的 1 加到 100 把它们和变量、复合赋值运算符完整串了一遍。

---

上一篇我们让程序学会了“看情况办事”：一个 `if` 判断完，最多执行一遍就结束。但真实程序里，重复才是常态——给全班 50 个同学判分，总不能写 50 遍 `if`；闹钟要每秒检查一次时间，更不能只检查一遍。

本篇只解决一个问题：**一段代码如何按条件反复执行，并且在该停的时候恰好停下来**。我们把这种能力叫**循环**（loop）。Java 的循环有几种写法，本篇把最常用的 `while` 和 `for` 讲透，`do-while` 和 for-each 留到文末各用一句话预告。

## while：先问“还要继续吗”，再干一轮活

`while` 的骨架和 `if` 长得几乎一样：

```java
while (条件) {
    循环体语句
}
```

括号里的条件同样必须是一个 boolean——上一篇学的“比较运算符当场算出 true 或 false”在这里原样适用。区别全在行为上：`if` 算完一次就散场，`while` 算完若是 true，就把整个循环体从头到尾执行一遍，**然后回到条件处再问一次**。如此反复，直到某一次条件算出 false，才跳出循环，继续执行循环体之后的代码。

官方教程对 `while` 的定义正是如此：只要条件为 true，它就持续执行语句块，不断重新测试表达式，直到表达式为 false [1]。

```mermaid
flowchart TD
    A[检查条件] -->|true| B[执行整个循环体]
    B --> A
    A -->|false| C[跳出循环，继续往后走]
```

这个“回到条件处再问一次”的回路就是循环的心脏。有一个推论值得先记住：如果条件一开始就是 false，循环体**一次都不会执行**——`while` 是先问再做，不是先做再问。

## for：把“数着次数干活”写成固定格式

看一个具体需求：打印 1、2、3 三个数。用 `while` 写是这样：

```java
int i = 1;                  // ① 准备：从 1 开始
while (i <= 3) {            // ② 检查：还没到 3 就继续
    System.out.println(i);  // ③ 干活：打印
    i++;                    // ④ 更新：i 加 1
}
```

四件事各就各位，但写多了容易漏——尤其是第④步，忘了它就成了死循环（后面会讲）。Java 把这种“数着次数干活”的常见套路压缩成了一行**头部**：

```java
for (int i = 1; i <= 3; i++) {
    System.out.println(i);
}
```

`for` 括号里用分号隔开三部分，官方教程逐一说明了它们的职责 [2]：

- **初始化**（`int i = 1`）：进入循环前只执行一次，负责准备计数器；
- **终止条件**（`i <= 3`）：每次循环开始前检查，算出 false 就结束整个循环；
- **更新**（`i++`）：**每一轮循环体执行完之后**执行，负责让计数器前进。

这里的 `i++` 是“把 `i` 的值加 1 再放回 `i`”的简写，等价于 `i = i + 1`。新手最容易搞错执行顺序：更新不是和条件一起在顶部检查的，它排在循环体之后、下一轮检查之前。完整走一遍上面那段代码：`i = 1` 执行一次 → 检查 `1 <= 3` 为 true → 打印 1 → `i` 变成 2 → 检查 `2 <= 3` 为 true → 打印 2 → `i` 变成 3 → 打印 3 → `i` 变成 4 → 检查 `4 <= 3` 为 false → 结束。输出恰好是 `1`、`2`、`3` 三行。

还有个细节：`i` 声明在 `for` 的头部，它的作用范围只到这个 `for` 循环结束为止，循环外面用不到它。官方教程明确建议：控制循环的变量只在循环内需要时，就声明在初始化表达式里，能少惹很多麻烦 [2]。拿 `i`、`j`、`k` 当计数器也是 Java 世界的长期惯例。

## 计数器从 0 还是 1 起步

两种写法都合法，差别只在你想表达什么。

**想数“1 到 n”**，就写 `i = 1; i <= n`。前面打印 1、2、3 用的就是它。

**想“恰好做 n 轮”**，Java 社区几乎总写 `i = 0; i < n`。原因会在学数组时彻底揭晓：数组的下标从 0 数起，第 1 个元素是 `array[0]`，所以遍历 n 个元素的惯用式是 `i < n`。

差一错误（off-by-one error）就埋伏在这两种写法之间：`i <= 3` 会跑 3 轮（i 取 1、2、3），而 `i < 3` 只跑 2 轮（i 取 1、2）。写循环前先问自己一句：“我想数到某个数，还是想走满某个次数？”这决定了用 `<=` 还是 `<`。

## 用 while 还是 for：看“要不要事先数好”

两条路都能写出同一个循环，但有一个朴素的判断标准：**循环次数在进入之前就知道，用 `for`；次数取决于运行过程中发生什么，用 `while`。**

“从 1 加到 100”是一眼能数清的任务，天然适合 `for`。而“从 1 开始不断翻倍，直到超过 100”该执行几轮，取决于 1 翻倍多少次才会越过 100——没人想提前数，用 `while` 更贴切：

```java
int n = 1;
while (n <= 100) {
    n = n * 2;
}
System.out.println(n);   // 128
```

每轮循环体把 `n` 变成原来的两倍：1、2、4、8、16、32、64、128。到 `n = 128` 时条件 `128 <= 100` 为 false，循环停下，打印 128。循环体里改的不是计数器，而是决定条件真假的那个状态——这正是 `while` 的主场。硬用 `for` 也能写，但要把翻倍动作塞进头部第三格，远不如 `while` 直白。

一句话总结：看到“重复 N 次”用 `for`，看到“重复直到某个状态发生”用 `while`。

## 三个新手坑：死循环、差一、多余的分号

**死循环。** 循环体里没有任何东西能让条件变假时，程序就永远跳不出来：

```java
int i = 1;
while (i <= 3) {
    System.out.println(i);
    // 忘了写 i++：i 永远是 1，条件恒为 true
}
```

这不是报错，而是程序“卡住”——控制台刷屏或毫无响应，比编译错误更难察觉，因为编译器认为代码完全合法。`for` 的三个部分其实都可以省略，写 `for (;;)` 也会造出一个死循环 [2]，有时是有意为之（配合后面会学的 `break` 跳出），但新手阶段先把它当事故。

**差一错误。** 就是上一节说的“多跑一轮、少跑一轮”。它不报错，只给出错的结果，是循环 bug 里最常见的一种。

**条件后误加分号。** 这是最阴险的一个：分号本身是一条“什么都不做的空语句”，而 Java 里循环体可以只有一条语句——

```java
int i = 1;
while (i <= 3); {        // 分号让空语句成了循环体！
    System.out.println(i);
}
```

`while` 现在只管辖那个分号：反复检查 `i <= 3`，而 `i` 永远不变，程序死循环在分号处，后面的花括号块成了循环外的普通代码块，一行也轮不到。`for` 版本同样中招：`for (int i = 0; i < 3; i++);` 会把空循环体跑完 3 轮，再把后面花括号里的代码当普通块执行一次——程序没死，但行为完全不是你想的那样。写完 `while (…)` 或 `for (…)` 后看一眼：**这一行末尾不该有分号**。

## 收尾例子：从 1 加到 100

把前面学的变量、条件、循环串成一个小程序——著名的 1 加到 100：

```java
int sum = 0;                       // 累加器，先装 0
for (int i = 1; i <= 100; i++) {   // i 从 1 数到 100
    sum += i;                      // 等价于 sum = sum + i
}
System.out.println(sum);           // 5050
```

有两点值得停下来看。`sum += i` 是“把 `sum` 和 `i` 相加，结果放回 `sum`”的简写，和 `i++` 是同一族运算符。`sum` 声明在循环**外面**，因为循环结束后还要用它；`i` 声明在 `for` 头部，循环一结束就作废——两类变量各有各的寿命。

观察这个循环，会发现它集齐了所有正常循环的三要素：开始前的准备（`sum = 0`、`i = 1`）、继续的条件（`i <= 100`）、每轮结束让条件朝“假”靠近一步的变化（`i++`）。缺了任何一件，程序要么不停、要么结果不对——对照前文的三个坑，它们本质上都是这三要素缺位。

循环家族还有两个成员本篇没展开：`do-while` 保证循环体至少执行一次（它把条件检查挪到了底部），for-each 则是遍历数组和集合时更简洁的写法——两者等到后面接触数组和集合时再正式见面。

## 来源

1. [Oracle Java 官方教程：while 与 do-while 语句的语法与执行机制](https://docs.oracle.com/javase/tutorial/java/nutsandbolts/while.html)
2. [Oracle Java 官方教程：for 语句三部分职责、变量作用域与 for(;;) 死循环](https://docs.oracle.com/javase/tutorial/java/nutsandbolts/for.html)

---

原文：https://pangzhengboyin.com/articles/java-while-vs-for-loops-9b108a49

> **庞征博引** · 想学的，慢慢都会
>
> 庞征博引是把想学的东西写成连载的 AI 学习工具。说出想学什么，它会先了解你的基础，再把主题写成一篇篇 5–10 分钟能读完的文章；边读边问，接下来学什么跟着你走。这篇就是这样写出来的。
>
> 开始你自己的连载 → https://pangzhengboyin.com
