循环结构、break、continue 与 Random | JavaSE

循环结构、break、continue 与 Random

一、学习目标

完成本章后,你应该能够:

  • 能够解释循环结构解决什么问题,并区分 forwhiledo-while 的典型使用场景。
  • 能够独立分析循环的初始化、条件判断、循环体和变量更新过程。
  • 能够正确设计循环边界,避免少执行一次、多执行一次和死循环。
  • 能够使用嵌套循环解决矩形、三角形、九九乘法表等二维重复问题。
  • 能够区分 breakcontinue 的控制效果。
  • 能够使用 Random 生成指定范围内的随机整数。
  • 能够把“循环 + 分支 + Random + 方法”组合起来解决猜数字、验证码、素数判断等综合问题。
  • 能够从“穷举、计数、累加、状态变化”等角度建立基础算法思维。

二、核心知识

2.1 为什么需要循环

假设要求输出 5 次:

Hello Java

最直接的写法:

System.out.println("Hello Java");
System.out.println("Hello Java");
System.out.println("Hello Java");
System.out.println("Hello Java");
System.out.println("Hello Java");

代码当然可以运行,但如果要求:

输出 100 次
输出 10000 次
处理 1000 名学生
遍历一批数据
不断让用户重新输入

重复手写显然不可行。

循环(Loop)的核心作用就是:

在满足某个条件的情况下,重复执行一段代码。

可以抽象成:

初始化
  ↓
判断条件
  ↓
条件成立?
 ┌───────┐
 是      否
 ↓       ↓
执行      结束
 ↓
更新状态
 ↓
再次判断

Java 基础阶段主要学习:

for
while
do-while

2.2 for 循环

基本语法:

for (初始化语句; 循环条件; 迭代语句) {
    循环体;
}

例如:

for (int i = 1; i <= 5; i++) {
    System.out.println("Hello Java");
}

这里可以拆成四部分:

int i = 1
↓
初始化

i <= 5
↓
循环条件

System.out.println(...)
↓
循环体

i++
↓
迭代语句

2.3 for 循环的完整执行过程

观察:

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

执行过程:

① int i = 1

② 判断 i <= 3
   1 <= 3 → true

③ 执行循环体
   输出 1

④ 执行 i++
   i = 2

⑤ 判断 i <= 3
   2 <= 3 → true

⑥ 输出 2

⑦ i++
   i = 3

⑧ 判断 3 <= 3
   true

⑨ 输出 3

⑩ i++
   i = 4

⑪ 判断 4 <= 3
   false

⑫ 循环结束

最终输出:

1
2
3

因此:

初始化通常只执行一次,而条件判断、循环体和迭代语句会不断重复。


2.4 循环变量

for (int i = 0; i < 10; i++) {
}

其中:

i

通常叫循环变量。

它承担:

记录当前执行到第几次
控制循环是否继续
提供当前索引或序号

等作用。

例如:

for (int i = 1; i <= 5; i++) {
    System.out.println("第 " + i + " 次");
}

2.5 为什么数组遍历经常从 0 开始

现在还没有正式进入数组,但可以先注意一个现象:

for (int i = 0; i < length; i++) {
}

在 Java 中非常常见。

原因是后面的数组索引从:

0

开始。

如果数组长度是:

5

合法索引就是:

0 1 2 3 4

所以:

i = 0;
i < 5;
i++;

天然对应全部索引。

这会在下一章正式使用。


2.6 累加思想

循环最常见的算法思想之一:

累加(Accumulation)。

例如计算:

1 + 2 + 3 + ... + 100

不能让一个变量每轮重新定义。

应该在循环外准备一个累加器:

int sum = 0;

for (int i = 1; i <= 100; i++) {
    sum += i;
}

System.out.println(sum);

过程:

sum = 0

i = 1 → sum = 1
i = 2 → sum = 3
i = 3 → sum = 6
...

这里:

sum += i;

表示不断把新数据累积到历史结果中。

这是算法中极其常见的思维。


2.7 计数思想

另一个重要思想:

计数(Counting)。

例如统计 1~100 中能被 3 整除的数字数量:

int count = 0;

for (int i = 1; i <= 100; i++) {
    if (i % 3 == 0) {
        count++;
    }
}

System.out.println(count);

这里:

i

表示当前检查的数据。

count

表示已经找到多少个符合条件的数据。

不要把:

遍历变量

与:

统计变量

混成同一个变量。


2.8 while 循环

基本格式:

while (循环条件) {
    循环体;
}

例如:

int i = 1;

while (i <= 5) {
    System.out.println(i);
    i++;
}

输出:

1
2
3
4
5

从功能上看,这段代码可以改成 for


2.9 for 与 while 的关系

例如:

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

可以写成:

int i = 1;

while (i <= 5) {
    System.out.println(i);
    i++;
}

因此:

forwhile 在表达能力上存在大量重叠。

源课程提供了一个很好用的经验规则:

一开始比较明确循环次数
→ for

一开始不知道具体循环多少次
→ while

这不是 Java 的语法限制,而是一种代码表达习惯。


2.10 什么时候 while 更自然

例如:

一张纸厚度 0.1 毫米,不断对折,什么时候厚度超过珠穆朗玛峰?

我们事先并不知道:

到底需要折多少次

因此很自然写成:

double thickness = 0.1;
double mountain = 8848860;

int count = 0;

while (thickness < mountain) {
    thickness *= 2;
    count++;
}

System.out.println(count);

这里真正控制循环的是:

厚度还没有达到目标

而不是:

固定循环 10 次

2.11 do-while 循环

基本格式:

do {
    循环体;
} while (条件);

例如:

int i = 1;

do {
    System.out.println(i);
    i++;
} while (i <= 5);

while 最大的结构差异是:

while
→ 先判断,再执行

do-while
→ 先执行,再判断

因此:

do-while 的循环体至少会执行一次。


2.12 while 与 do-while 的区别

例如:

int number = 10;

while (number < 5) {
    System.out.println("while");
}

第一次判断:

10 < 5 → false

所以循环体:

0 次

而:

int number = 10;

do {
    System.out.println("do-while");
} while (number < 5);

会先执行:

1 次

然后才发现条件为 false

因此天然适合“至少做一次”的业务。


2.13 死循环

如果循环永远无法到达结束条件,就形成:

死循环(Infinite Loop)。

例如:

while (true) {
    System.out.println("Hello");
}

理论上会一直执行。

死循环并不一定意味着代码写错。

有些业务本来就是:

不断接收请求
不断显示菜单
不断等待输入
游戏主循环
服务器持续运行

但这种循环必须设计可靠的:

退出条件

例如:

while (true) {
    int command = sc.nextInt();

    if (command == 0) {
        break;
    }
}

2.14 一个典型的循环错误:忘记更新变量

例如:

int i = 1;

while (i <= 5) {
    System.out.println(i);
}

这里:

i

永远是:

1

所以条件:

i <= 5

永远为 true

于是变成死循环。

正确逻辑需要:

i++;

2.15 循环边界:<<=

观察:

for (int i = 1; i <= 5; i++)

产生:

1 2 3 4 5

共 5 次。

而:

for (int i = 1; i < 5; i++)

产生:

1 2 3 4

共 4 次。

这是最常见的:

Off-by-One Error,边界差一错误。

写循环时应该问:

从哪里开始?
最后一个合法值是什么?
边界应该包含还是不包含?
变量每次怎样变化?

而不是只凭感觉写 <<=


2.16 循环嵌套

循环里面还可以放循环:

for (...) {
    for (...) {
    }
}

叫:

循环嵌套(Nested Loop)。

例如打印:

*****
*****
*****

可以理解:

外层循环
→ 控制行

内层循环
→ 控制每一行有多少列

代码:

for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 5; j++) {
        System.out.print("*");
    }

    System.out.println();
}

2.17 从二维坐标理解嵌套循环

外层:

for (int i = 0; i < rows; i++)

可以理解为:

当前第几行

内层:

for (int j = 0; j < columns; j++)

可以理解为:

当前第几列

于是形成:

(i, j)

(0,0) (0,1) (0,2)
(1,0) (1,1) (1,2)
(2,0) (2,1) (2,2)

这也是下一章二维数组遍历的直接基础。


2.18 星号三角形

例如:

*
**
***
****
*****

规律:

第 1 行 → 1 个 *
第 2 行 → 2 个 *
第 3 行 → 3 个 *
...

所以:

for (int i = 1; i <= 5; i++) {

    for (int j = 1; j <= i; j++) {
        System.out.print("*");
    }

    System.out.println();
}

这里最核心的是发现:

内层循环次数 = 当前行号

2.19 九九乘法表

同样可以把它理解为二维结构:

for (int i = 1; i <= 9; i++) {

    for (int j = 1; j <= i; j++) {
        System.out.print(
                j + " × " + i + " = " + (j * i) + "\t"
        );
    }

    System.out.println();
}

这里再次体现:

外层控制行
内层控制当前行中的元素数量

2.20 break

break 的作用:

立即结束当前所在的循环。

例如:

for (int i = 1; i <= 10; i++) {

    if (i == 5) {
        break;
    }

    System.out.println(i);
}

输出:

1
2
3
4

当:

i == 5

执行 break 后:

整个 for 循环结束

不会再执行:

6 7 8 9 10

2.21 continue

continue 的作用:

跳过当前这一轮循环剩余代码,进入下一轮循环。

例如:

for (int i = 1; i <= 5; i++) {

    if (i == 3) {
        continue;
    }

    System.out.println(i);
}

输出:

1
2
4
5

当:

i == 3

只跳过:

当前第 3 轮

循环本身并没有结束。


2.22 break 与 continue 对比

可以记住:

break
↓
整个当前循环不要了


continue
↓
当前这一轮不要了

例如:

循环 1~10

break at 5
→ 1 2 3 4


continue at 5
→ 1 2 3 4 6 7 8 9 10

2.23 嵌套循环中的 break

观察:

for (int i = 1; i <= 3; i++) {

    for (int j = 1; j <= 3; j++) {

        if (j == 2) {
            break;
        }

        System.out.println(i + "," + j);
    }
}

里面的:

break;

默认结束的是:

它所在的最近一层循环。

也就是内层 for

外层循环仍然继续。

所以多层循环中的:

break / continue

一定要判断:

它到底属于哪一层循环?


2.24 Random 是什么

Java 提供:

java.util.Random

用于生成:

伪随机数据。

基础使用需要导包:

import java.util.Random;

然后创建对象:

Random random = new Random();

再调用相关方法。

例如:

int number = random.nextInt(10);

可以得到:

0 ~ 9

之间的随机整数。

注意:

包含 0
不包含 10

2.25 nextInt(bound) 的范围

例如:

random.nextInt(10)

表示:

[0, 10)

即:

0 1 2 3 4 5 6 7 8 9

因此:

下界 0 包含
上界 bound 不包含

这是以后大量 API 都会看到的:

左闭右开区间。


2.26 生成 1~10

源课程采用非常经典的“减加法”:

int number = random.nextInt(10) + 1;

先生成:

0 ~ 9

再整体:

+1

得到:

1 ~ 10

可以画成:

0 1 2 ... 9
↓ 全部 +1
1 2 3 ... 10

2.27 生成任意 [min, max] 整数

传统思路:

希望得到:

[min, max]

元素数量:

max - min + 1

先生成:

0 ~ max-min

再整体加:

min

因此:

int number =
        random.nextInt(max - min + 1) + min;

例如 65~91:

int number = random.nextInt(27) + 65;

因为:

91 - 65 + 1 = 27

先得到:

0 ~ 26

再:

+ 65

最终:

65 ~ 91

2.28 JDK 21 的直接范围写法

在 JDK 21 环境中,Random 还可以使用:

int number = random.nextInt(65, 92);

这里:

65
↓
包含

92
↓
不包含

因此结果仍然是:

65 ~ 91

也就是说现代 Java 可以直接表达:

[origin, bound)

但是必须理解左闭右开的区间,否则依旧容易出现边界错误。


2.29 Random 并不代表真正不可预测

Random 适合:

学习案例
小游戏
普通随机模拟
洗牌练习
随机测试数据

但对于:

密码
安全令牌
密钥
验证码安全凭据

不能因为名字叫:

Random

就认为它一定满足密码学安全要求。

安全敏感场景应该使用专门的安全随机数方案。

本章只学习基础随机数逻辑,不展开密码学随机数。


三、使用方法

3.1 计算 1~100 的和

public class SumDemo {

    public static void main(String[] args) {

        int sum = 0;

        for (int i = 1; i <= 100; i++) {
            sum += i;
        }

        System.out.println(sum);
    }
}

算法模型:

准备结果变量
↓
逐个访问数据
↓
把当前数据累计到结果

3.2 计算奇数和

int sum = 0;

for (int i = 1; i <= 100; i++) {

    if (i % 2 != 0) {
        sum += i;
    }
}

System.out.println(sum);

这里组合了:

循环
+
分支
+
累加

3.3 登录重试

int failures = 0;

while (failures < 3) {

    boolean success = false;

    if (success) {
        System.out.println("登录成功");
        break;
    }

    failures++;
}

真正项目中会结合 Scanner 和真实密码判断。

当前重点是理解:

最多尝试若干次
+
成功后提前 break

3.4 跳过 3 的倍数

for (int i = 1; i <= 100; i++) {

    if (i % 3 == 0) {
        continue;
    }

    System.out.println(i);
}

这里的业务含义非常自然:

是 3 的倍数?
→ 跳过当前轮

不是?
→ 正常处理

3.5 猜数字

import java.util.Random;
import java.util.Scanner;

public class GuessNumberDemo {

    public static void main(String[] args) {

        Scanner scanner = new Scanner(System.in);
        Random random = new Random();

        int target = random.nextInt(100) + 1;

        while (true) {

            System.out.print("请输入 1~100 的数字:");
            int guess = scanner.nextInt();

            if (guess > target) {
                System.out.println("猜大了");
            } else if (guess < target) {
                System.out.println("猜小了");
            } else {
                System.out.println("猜中了");
                break;
            }
        }
    }
}

这个案例已经综合:

Scanner
Random
while
if
break

这说明 Java 基础知识正在开始真正组合起来。


3.6 判断素数的基本思路

素数:

大于 1,并且除了 1 和自身以外不能被其他正整数整除。

例如判断 number

boolean prime = number > 1;

for (int i = 2; i < number; i++) {

    if (number % i == 0) {
        prime = false;
        break;
    }
}

思维本质:

枚举可能的因子
↓
发现一个能整除的因子
↓
立即知道不是素数
↓
break

这是:

枚举 + 剪枝

的最基础形态。


3.7 水仙花数中的数字拆分

对于三位数:

abc

可以利用:

int ones = number % 10;
int tens = number / 10 % 10;
int hundreds = number / 100;

然后判断:

ones * ones * ones
        +
tens * tens * tens
        +
hundreds * hundreds * hundreds
        ==
number

这把前面学过的:

/
%
循环
if

组合成了一个算法问题。


四、原理与进阶

4.1 循环问题的四问模型

以后看到循环题,可以先问四个问题:

① 我要重复做什么?

② 从什么时候开始?

③ 在什么条件下继续?

④ 每一轮结束后,状态如何变化?

对应:

for (
    初始化;
    条件;
    更新
) {
    重复任务;
}

循环不是语法背诵,而是:

状态不断变化直到满足终止条件。


4.2 循环不变量

例如求和:

int sum = 0;

for (int i = 1; i <= 100; i++) {
    sum += i;
}

每轮结束时:

sum

都代表:

到目前为止已经处理的数据之和。

这种在循环过程中持续成立的规律,是理解算法正确性的重要入口。

后面真正学习算法时,会进一步接触:

Loop Invariant,循环不变量。


4.3 break 是一种提前终止优化

假设判断:

97 是否是素数

一旦发现:

97 % i == 0

就已经能确定答案。

没必要再继续检查剩余候选值。

因此:

break;

不仅是控制语法,还体现:

当结果已经确定时,停止无意义的后续工作。


4.4 时间复杂度的初步直觉

一个循环:

for (int i = 0; i < n; i++) {
}

通常执行大约:

n 次

两层完整嵌套:

for (...) {
    for (...) {
    }
}

如果两层都与 n 成比例,执行次数可能接近:

n × n

即:

现在不要求正式掌握复杂度分析,但必须开始产生直觉:

循环层数和循环范围会直接影响程序执行工作量。


五、实践应用

循环几乎存在于所有软件中。

例如:

批量处理订单
遍历数据库结果
发送消息
游戏帧循环
重试机制
分页处理
文件读取
网络服务器持续监听
算法搜索

Random 则经常用于:

游戏随机事件
抽样
测试数据
随机顺序
模拟实验
普通洗牌

所以这一章实际上连接了:

基础语法
↓
算法
↓
真实程序控制流程

六、常见问题

6.1 for 和 while 哪个更强?

没有必要比较谁“更强”。

很多场景可以互相改写。

一般经验:

次数明确
→ for 更自然

终止条件明确但次数未知
→ while 更自然

6.2 do-while 一定会执行一次吗?

是。

因为它:

先执行
后判断

6.3 break 会结束整个程序吗?

不会。

它通常结束:

当前所在循环。

例如:

for (...) {
    break;
}

System.out.println("程序继续");

循环结束后,后面的代码仍然可以继续执行。


6.4 continue 会结束循环吗?

不会。

它只结束:

当前这一轮。

之后进入下一轮循环。


6.5 为什么 while(true) 不一定是 Bug?

因为一些程序本来就需要持续运行。

关键是:

是否存在合理退出机制,以及无限运行是否符合业务设计。


6.6 为什么 Random 的上界经常不包含?

例如:

random.nextInt(10)

使用:

[0, 10)

这种左闭右开范围。

它与 Java 中大量索引和区间设计非常协调。


6.7 生成 1~10 为什么不能写 nextInt(10)

因为:

nextInt(10)

得到:

0~9

而不是:

1~10

需要进行区间平移:

random.nextInt(10) + 1;

6.8 多层循环中的 break 会退出几层?

普通:

break;

默认退出最近的那一层循环。

因此复杂嵌套时必须明确控制范围。


6.9 为什么循环经常出现多一次或少一次?

通常来自:

初始值错误
< 与 <= 错误
++ 位置错误
循环次数理解错误

所以写循环前应该先列出:

第一个值
最后一个值
总次数

七、练习与验收

7.1 知识问答

  1. 循环结构解决什么问题?
  2. for 循环由哪些主要部分组成?
  3. 请完整描述 for 一轮执行过程。
  4. forwhile 在使用习惯上有什么区别?
  5. whiledo-while 最核心的区别是什么?
  6. 什么是死循环?
  7. 什么场景可能主动使用 while(true)
  8. 什么是循环嵌套?
  9. 外层和内层循环分别如何理解?
  10. breakcontinue 有什么区别?
  11. 嵌套循环中的普通 break 默认结束哪一层?
  12. Random 用于解决什么问题?
  13. nextInt(10) 的范围是什么?
  14. 如何生成 1~10?
  15. 如何生成任意 [min,max] 整数?
  16. JDK 21 中如何直接表达 [65,92) 的随机整数范围?
  17. 素数判断为什么适合使用 break
  18. 什么是累加思想?
  19. 什么是计数思想?
  20. 为什么双重循环通常比单循环执行更多工作?

7.2 代码阅读

禁止运行:

public class LoopRead01 {

    public static void main(String[] args) {

        int sum = 0;

        for (int i = 1; i <= 5; i++) {

            if (i == 3) {
                continue;
            }

            sum += i;
        }

        System.out.println(sum);
    }
}

要求:

  1. 逐轮写出 i
  2. 哪一轮没有执行 sum += i
  3. 预测最终结果。
  4. 如果把 continue 改为 break,执行过程如何变化?

继续分析:

public class LoopRead02 {

    public static void main(String[] args) {

        for (int i = 1; i <= 3; i++) {

            for (int j = 1; j <= 3; j++) {

                if (j == 2) {
                    break;
                }

                System.out.println(i + "-" + j);
            }
        }
    }
}

要求:

  1. 预测所有输出。
  2. 判断 break 结束哪一层循环。
  3. 外层循环一共执行多少次?

7.3 手写代码

  1. 使用 for 计算 1~100 的和。
  2. 计算 1~100 所有奇数之和。
  3. 计算 1 × 2 × ... × n
  4. 使用 while 完成纸张折叠高度案例。
  5. 使用 do-while 设计一个至少显示一次的菜单。
  6. 打印 5 行星号三角形。
  7. 打印九九乘法表。
  8. 遍历 1~100,使用 continue 跳过所有 3 的倍数。
  9. 设计最多允许三次失败的登录重试。
  10. 使用 Random 生成 1~100 的随机整数。

7.4 Debug

public class LoopDebug01 {

    public static void main(String[] args) {

        int i = 1;

        while (i <= 10) {
            System.out.println(i);
        }
    }
}

要求:

  1. 判断程序最核心的问题。
  2. 为什么循环不能结束?
  3. 修复代码。

继续:

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

需求是输出:

1 2 3 4 5

要求:

  1. 找出边界问题。
  2. 至少设计两种正确写法。
  3. 总结如何避免 Off-by-One Error。

7.5 综合训练

完成以下综合任务。

任务一:猜数字

随机生成:

1~100

用户不断输入数字:

猜大 → 提示猜大了
猜小 → 提示猜小了
猜中 → 结束

要求使用:

Random
Scanner
while
if
break

任务二:随机验证码

生成 5 位验证码,每一位可以是:

数字
大写字母
小写字母

要求先写出:

流程图或伪代码

再编写 Java。

任务三:寻找素数

输出:

2~1000

中的所有素数并统计数量。

要求思考:

什么时候可以提前 break?
是否必须检查到 number - 1?

后一个问题作为算法优化思考,不要求本章立即掌握最优方案。

7.6 本章验收

关闭资料后:

  • [ ] 能从零写 for
  • [ ] 能从零写 while
  • [ ] 能从零写 do-while
  • [ ] 能解释三种循环差异。
  • [ ] 能正确设计循环边界。
  • [ ] 能解决累加和计数问题。
  • [ ] 能写双层循环。
  • [ ] 能解释 break
  • [ ] 能解释 continue
  • [ ] 能使用 Random.nextInt(bound)
  • [ ] 能计算指定随机数范围。
  • [ ] 能把循环、if、Random、Scanner 组合起来。
  • [ ] 遇到循环题时能够先分析“初始状态、继续条件、每轮任务、状态更新”。

如果目前仍然主要通过:

“试着改 <<=,直到结果碰巧正确”

来写循环,那么循环控制还没有真正掌握。