循环结构、break、continue 与 Random | JavaSE
循环结构、break、continue 与 Random
一、学习目标
完成本章后,你应该能够:
- 能够解释循环结构解决什么问题,并区分
for、while、do-while的典型使用场景。 - 能够独立分析循环的初始化、条件判断、循环体和变量更新过程。
- 能够正确设计循环边界,避免少执行一次、多执行一次和死循环。
- 能够使用嵌套循环解决矩形、三角形、九九乘法表等二维重复问题。
- 能够区分
break与continue的控制效果。 - 能够使用
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++;
}
因此:
for和while在表达能力上存在大量重叠。
源课程提供了一个很好用的经验规则:
一开始比较明确循环次数
→ 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
即:
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 知识问答
- 循环结构解决什么问题?
for循环由哪些主要部分组成?- 请完整描述
for一轮执行过程。 for与while在使用习惯上有什么区别?while与do-while最核心的区别是什么?- 什么是死循环?
- 什么场景可能主动使用
while(true)? - 什么是循环嵌套?
- 外层和内层循环分别如何理解?
break与continue有什么区别?- 嵌套循环中的普通
break默认结束哪一层? Random用于解决什么问题?nextInt(10)的范围是什么?- 如何生成 1~10?
- 如何生成任意
[min,max]整数? - JDK 21 中如何直接表达
[65,92)的随机整数范围? - 素数判断为什么适合使用
break? - 什么是累加思想?
- 什么是计数思想?
- 为什么双重循环通常比单循环执行更多工作?
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);
}
}
要求:
- 逐轮写出
i。 - 哪一轮没有执行
sum += i? - 预测最终结果。
- 如果把
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);
}
}
}
}
要求:
- 预测所有输出。
- 判断
break结束哪一层循环。 - 外层循环一共执行多少次?
7.3 手写代码
- 使用
for计算 1~100 的和。 - 计算 1~100 所有奇数之和。
- 计算
1 × 2 × ... × n。 - 使用
while完成纸张折叠高度案例。 - 使用
do-while设计一个至少显示一次的菜单。 - 打印 5 行星号三角形。
- 打印九九乘法表。
- 遍历 1~100,使用
continue跳过所有 3 的倍数。 - 设计最多允许三次失败的登录重试。
- 使用
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);
}
}
}
要求:
- 判断程序最核心的问题。
- 为什么循环不能结束?
- 修复代码。
继续:
for (int i = 1; i < 5; i++) {
System.out.println(i);
}
需求是输出:
1 2 3 4 5
要求:
- 找出边界问题。
- 至少设计两种正确写法。
- 总结如何避免 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 组合起来。
- [ ] 遇到循环题时能够先分析“初始状态、继续条件、每轮任务、状态更新”。
如果目前仍然主要通过:
“试着改
<、<=,直到结果碰巧正确”
来写循环,那么循环控制还没有真正掌握。