Java流程控制详解:掌握if、switch、for和while循环
文章目录
- Java流程控制详解:掌握if、switch、for和while循环
-
- 前言
- 一、顺序结构
- 二、选择结构------if语句
-
- [2.1 单分支if语句](#2.1 单分支if语句)
- [2.2 if-else双分支语句](#2.2 if-else双分支语句)
- [2.3 if-else if-else多分支语句](#2.3 if-else if-else多分支语句)
- [2.4 if语句的嵌套](#2.4 if语句的嵌套)
- [2.5 if语句的简化写法](#2.5 if语句的简化写法)
- [2.6 if-else和switch的选择原则](#2.6 if-else和switch的选择原则)
- 三、选择结构------switch语句
-
- [3.1 基本语法](#3.1 基本语法)
- [3.2 switch支持的表达式类型](#3.2 switch支持的表达式类型)
- [3.3 case穿透现象](#3.3 case穿透现象)
- [3.4 JDK 14+ 增强版switch](#3.4 JDK 14+ 增强版switch)
- 四、循环结构------for循环
-
- [4.1 基本for循环](#4.1 基本for循环)
- [4.2 for循环的变体](#4.2 for循环的变体)
- [4.3 经典for循环练习](#4.3 经典for循环练习)
- [4.4 增强型for循环(for-each)](#4.4 增强型for循环(for-each))
- [5.3 循环的选择建议](#5.3 循环的选择建议)
- 五、循环结构------while循环
-
- [5.1 while循环](#5.1 while循环)
- [5.2 do-while循环](#5.2 do-while循环)
- 六、循环控制关键字
-
- [6.1 break------跳出循环](#6.1 break——跳出循环)
- [6.2 continue------跳过本次循环](#6.2 continue——跳过本次循环)
- [6.3 带标签的break和continue](#6.3 带标签的break和continue)
- [6.4 死循环与退出](#6.4 死循环与退出)
- 七、综合案例------学生成绩管理系统(简化版)
- 总结
- 综合练习题(纸笔推理)
- [✅ 亮点总结](#✅ 亮点总结)
- 适用场景
- 扩展方向
前言
在程序的世界中,代码通常不是按顺序一行行执行的。我们经常需要根据条件做出不同选择,或者重复执行某段代码。这就是流程控制要做的事情。Java提供了丰富的流程控制语句,主要分为三大类:顺序结构、选择结构(条件判断)和循环结构。本文将带你逐一掌握这些语法,并给出大量实际可运行的代码示例。
一、顺序结构
顺序结构是最基本的程序结构,代码按照从上到下的顺序依次执行。如果没有流程控制语句,所有程序都是顺序执行的。
java
public class SequenceDemo {
public static void main(String[] args) {
System.out.println("第一步:打开冰箱门");
System.out.println("第二步:把大象放进去");
System.out.println("第三步:关上冰箱门");
// 按顺序1→2→3执行
}
}
二、选择结构------if语句
2.1 单分支if语句
java
// 语法格式
if (条件表达式) {
// 条件为true时执行的代码块
}
// 实际示例:判断年龄
int age = 20;
if (age >= 18) {
System.out.println("你已经是成年人了!");
}
执行流程 :先判断条件表达式,如果为true就执行花括号内的代码块;如果为false就跳过。
执行流程图解:
程序开始
│
▼
┌─────────────┐
│ 判断条件表达式 │
└──────┬──────┘
│
┌───┴───┐
true false
│ │
▼ ▼
┌──────┐ (跳过代码块)
│执行体│ │
└──┬───┘ │
│ │
└────┬─────┘
▼
后续代码继续执行
容易踩的坑:
- 条件表达式必须返回
boolean类型 ,不能写成if (age = 18)(那是赋值语句,不是比较)。Java中if只接受boolean,这一点和C语言不同,C语言中非零即真。 - 不要直接在
if条件中写赋值语句 ,例如if (flag = true)虽然语法上可能通过编译(如果flag是boolean类型),但逻辑上是一个常见错误------本意是比较,实际是赋值后取赋值结果。 - 浮点数不建议直接用于等值判断 ,因为浮点数计算存在精度误差。例如
if (0.1 + 0.2 == 0.3)结果是false,应改用差值小于一个极小值的方式判断。
2.2 if-else双分支语句
java
// 语法格式
if (条件表达式) {
// 条件为true时执行
} else {
// 条件为false时执行
}
// 实际示例:判断奇偶数
int number = 7;
if (number % 2 == 0) {
System.out.println(number + "是偶数");
} else {
System.out.println(number + "是奇数");
}
执行流程图解:
程序开始
│
▼
┌─────────────┐
│ 判断条件表达式 │
└──────┬──────┘
│
┌───┴───┐
true false
│ │
▼ ▼
┌──────┐ ┌──────┐
│if体 │ │else体│
└──┬───┘ └──┬───┘
│ │
└────┬─────┘
▼
后续代码继续执行
悬空else问题(Dangling Else) :这是初学者最容易掉入的陷阱之一。当if语句嵌套且省略花括号时,else总是与**最近的、尚未配对的if**配对。看下面的例子:
java
// 你以为else匹配的是外层if?
int a = 5, b = 10;
if (a > 3)
if (b > 20)
System.out.println("条件1");
else
System.out.println("条件2");
// 实际输出:条件2
分析 :你可能以为else属于第一个if(a > 3),但实际上编译器将它绑定到了最近的if(b > 20)。因为a > 3为true,进入内层if;但b > 20为false,所以匹配的else被执行。如果你想让else属于外层if,必须使用花括号明确区分:
java
if (a > 3) {
if (b > 20)
System.out.println("条件1");
} else {
System.out.println("条件2");
}
// 此时不会输出任何东西,因为外层if为true,else不执行
最佳实践 :无论代码块有多少行,始终使用花括号,这样既避免悬空else问题,也让代码结构一目了然。
2.3 if-else if-else多分支语句
java
// 评分等级判断
int score = 85;
if (score >= 90) {
System.out.println("优秀");
} else if (score >= 80) {
System.out.println("良好");
} else if (score >= 70) {
System.out.println("中等");
} else if (score >= 60) {
System.out.println("及格");
} else {
System.out.println("不及格");
}
// 输出:良好
注意事项:
- 多个条件有顺序性,一旦某个条件为
true,后面的条件不再判断 else if可以有0到多个,else最多一个- 在多个
else if中,将最可能为真的条件放在前面可以提高判断效率,因为一旦命中就不会继续判断后续条件 - 条件顺序很重要 :如果把
score >= 60放在最前面,那么所有60分以上的成绩都会被判定为"及格",永远不会进入"良好"和"优秀"的判断
2.4 if语句的嵌套
java
// 判断闰年:能被4整除但不能被100整除,或者能被400整除
int year = 2024;
if (year % 4 == 0) {
if (year % 100 != 0) {
System.out.println(year + "是闰年");
} else if (year % 400 == 0) {
System.out.println(year + "是闰年");
} else {
System.out.println(year + "不是闰年");
}
} else {
System.out.println(year + "不是闰年");
}
2.5 if语句的简化写法
当代码块只有一行语句时,可以省略花括号(但不推荐,容易引发错误):
java
if (score >= 60)
System.out.println("及格了!");
else
System.out.println("继续加油!");
再次强调 :省略花括号虽然省了一行代码,但后期维护时极易出错。例如有人在
if后面多加了第二行语句,以为它属于if块,但实际上并不属于。永远加上花括号是最好的编码习惯。
2.6 if-else和switch的选择原则
在实际开发中,很多人困惑到底该用if-else还是switch。下面从多个维度进行对比:
| 对比维度 | if-else | switch |
|---|---|---|
| 适用场景 | 范围判断(>, <, !=等) |
等值匹配(==精确匹配) |
| 条件类型 | 任意boolean表达式 |
整数、字符串、枚举 |
| 代码可读性 | 分支多时嵌套复杂,可读性下降 | 分支多时结构清晰,一目了然 |
| 执行效率 | 逐一判断直到命中,O(n) | 编译器可优化为跳转表,O(1) |
| 灵活性 | 高,支持复杂逻辑组合 | 低,只能做等值匹配 |
| break需求 | 不需要 | 每个case需要break(传统写法) |
| 维护成本 | 增加分支只需加一个else if |
增加分支只需加一个case |
选择建议:
- 3个以下分支 :
if-else和switch都可以,差异不大 - 3~10个等值分支 :优先使用
switch,代码更整洁,且编译器可能优化为跳转表提升性能 - 涉及范围判断 :只能用
if-else,switch不支持范围匹配(JDK 14+箭头语法可通过组合case近似实现) - 条件涉及多个变量 :只能用
if-else,因为switch只有一个表达式入口
实际案例对比:
java
// ✅ 好:等值判断用switch
switch (statusCode) {
case 200: handleSuccess(); break;
case 404: handleNotFound(); break;
case 500: handleServerError(); break;
default: handleUnknown(); break;
}
// ❌ 不好:等值判断强行用if-else(分支多了可读性差)
if (statusCode == 200) {
handleSuccess();
} else if (statusCode == 404) {
handleNotFound();
} else if (statusCode == 500) {
handleServerError();
} else {
handleUnknown();
}
// ✅ 好:范围判断必须用if-else
if (temperature > 35) {
System.out.println("高温预警");
} else if (temperature > 25) {
System.out.println("舒适温度");
} else if (temperature > 10) {
System.out.println("偏凉");
} else {
System.out.println("寒冷");
}
三、选择结构------switch语句
3.1 基本语法
switch语句用于等值判断的场景,比多个if-else if更清晰。它的底层实现原理是:编译器在编译时会生成一个跳转表(Jump Table),运行时直接通过索引跳转,不需要逐个比较,因此当case较多时效率明显高于if-else:
java
// switch语句执行流程(伪代码)
// JVM读取day的值 → 在跳转表中查找匹配的case → 直接跳转对应位置
// 而不是 case1→不匹配→case2→不匹配→case3→匹配!(那样逐个比较效率低)
```java
int day = 3;
String dayName;
switch (day) {
case 1:
dayName = "星期一";
break;
case 2:
dayName = "星期二";
break;
case 3:
dayName = "星期三";
break;
case 4:
dayName = "星期四";
break;
case 5:
dayName = "星期五";
break;
case 6:
dayName = "星期六";
break;
case 7:
dayName = "星期日";
break;
default:
dayName = "无效日期";
break;
}
System.out.println("今天是" + dayName); // 今天是星期三
3.2 switch支持的表达式类型
byte、short、int、char- 对应的包装类:
Byte、Short、Integer、Character String(JDK 7+)Enum枚举类型
java
// JDK 7+支持String类型
String season = "spring";
switch (season) {
case "spring":
System.out.println("春暖花开");
break;
case "summer":
System.out.println("夏日炎炎");
break;
case "autumn":
System.out.println("秋高气爽");
break;
case "winter":
System.out.println("冬日暖阳");
break;
default:
System.out.println("未知季节");
break;
}
注意 :
switch匹配字符串时,实际是通过String.equals()和hashCode()来实现的,是区分大小写 的。同时,case后面的字符串不能为null,否则会抛出NullPointerException。
java
// 枚举类型配合switch ------ 最推荐的做法
enum Day {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
Day today = Day.FRIDAY;
switch (today) {
case MONDAY:
case TUESDAY:
case WEDNESDAY:
case THURSDAY:
case FRIDAY:
System.out.println("工作日,继续加油!");
break;
case SATURDAY:
case SUNDAY:
System.out.println("周末,好好休息!");
break;
}
为什么推荐枚举配合switch?
- 类型安全:枚举限制了取值集合,不会传入非法值(如"星期八"),IDE也能自动补全
- 可读性高 :
Day.MONDAY比1更有语义意义 - 编译期检查 :如果你缺少某个枚举值的
case,IDE可以给出警告 - default分支可选 :如果你覆盖了所有枚举值,可以省略
default(但建议保留,防止后续新增枚举值遗漏case)
java
// 另一个枚举+switch的实战案例:订单状态处理
enum OrderStatus {
PENDING, CONFIRMED, SHIPPED, DELIVERED, CANCELLED
}
OrderStatus status = OrderStatus.CONFIRMED;
switch (status) {
case PENDING:
System.out.println("订单待确认,请等待商家处理");
break;
case CONFIRMED:
System.out.println("订单已确认,正在准备发货");
break;
case SHIPPED:
System.out.println("订单已发货,请注意查收");
break;
case DELIVERED:
System.out.println("订单已送达,请确认收货");
break;
case CANCELLED:
System.out.println("订单已取消");
break;
default:
System.out.println("未知状态");
break;
}
3.3 case穿透现象
如果没有break,程序会一直向下执行到下一个break或switch结束------这被称为"穿透":
java
int month = 8;
switch (month) {
case 6:
case 7:
case 8:
System.out.println("夏季");
break; // 6、7、8都会打印"夏季"
case 9:
case 10:
case 11:
System.out.println("秋季");
break;
default:
System.out.println("其他季节");
break;
}
合理利用穿透可以减少重复代码,但忘记写break是初学者最容易犯的错误之一。
穿透的实际应用场景汇总:
java
// 场景1:合并多个相同逻辑的case(季节归类)
int month = 8;
switch (month) {
case 3: case 4: case 5:
System.out.println("春季"); break;
case 6: case 7: case 8:
System.out.println("夏季"); break;
case 9: case 10: case 11:
System.out.println("秋季"); break;
case 12: case 1: case 2:
System.out.println("冬季"); break;
}
// 场景2:阶梯式计算(如个税累进税率)
int salary = 15000;
int tax = 0;
switch (salary / 5000) {
case 7: // salary >= 35000
tax += (salary - 35000) * 0.3;
salary = 35000;
case 6: // 30000~35000
tax += (salary - 30000) * 0.25;
salary = 30000;
case 5: // 25000~30000
tax += (salary - 25000) * 0.2;
salary = 25000;
// ... 以此类推
default:
tax += salary * 0.03;
break;
}
// 注意:这种写法利用了穿透特性,但代码非常危险,实际开发建议用if-else实现
警告 :场景2中的阶梯式计算虽然利用了穿透,但阅读和维护成本极高,且容易引入bug。实际项目中不推荐这样写,仅作为展示穿透机制的一个例子。
忘记break的调试技巧 :如果你的switch分支出现"意外输出"(比如匹配的是case 1却同时执行了case 2的内容),几乎可以肯定是某个case缺少break,排查时从匹配的case向下逐行检查即可。
3.4 JDK 14+ 增强版switch
从JDK 14开始,switch有了箭头语法,不需要break也不会穿透:
java
// 箭头语法:简洁的表达式风格
int dayNum = 3;
String result = switch (dayNum) {
case 1, 2, 3, 4, 5 -> "工作日";
case 6, 7 -> "休息日";
default -> "无效日期";
};
System.out.println(result);
箭头语法的优势:
- 无需break:每个分支只执行自己的语句,不会穿透到下一个case
- 支持多值合并 :
case 1, 2, 3, 4, 5 ->一行搞定多个值 - 可作为表达式:switch可以返回值赋给变量(如上例)
- 编译器强制覆盖:如果遗漏某个case值,编译器会给出警告
使用yield返回多行代码块的值:
当某个分支需要执行多行语句时,使用花括号包裹,并通过yield关键字返回值:
java
String activity = switch (dayNum) {
case 1, 2, 3, 4, 5 -> {
System.out.println("今天是工作日");
yield "去上班"; // yield 用于返回花括号块的值
}
case 6, 7 -> {
System.out.println("今天是周末");
String plan = "去运动";
yield plan; // yield 必须写在花括号块的最后
}
default -> "躺在家里";
};
System.out.println("计划:" + activity);
注意 :
yield是JDK 14引入的关键字,但在JDK 14之前它不是保留字,可以作为变量名使用。如果你的代码中把yield用作变量名,升级JDK后需要修改。
新旧switch对比总结:
| 特性 | 传统switch | 增强switch (JDK 14+) |
|---|---|---|
| case语法 | case X: |
case X -> 或 case X: |
| 穿透行为 | 默认穿透,需要break | 箭头语法不穿透 |
| 多值合并 | 利用穿透实现 | case 1, 2, 3 -> |
| 作为表达式 | 不支持 | 支持(用switch整体赋值) |
| 返回值 | 不支持 | 用yield返回 |
| 编译检查 | 无 | 缺少case时警告 |
四、循环结构------for循环
4.1 基本for循环
java
// 语法格式
for (初始化语句; 条件判断; 条件控制) {
// 循环体
}
// 打印1到10
for (int i = 1; i <= 10; i++) {
System.out.println("当前数字:" + i);
}
执行顺序详解:
┌──────────────────┐
│ ① 初始化语句 │ ← 只执行一次
│ (int i = 1) │
└────────┬─────────┘
▼
┌──────────────────┐
┌───→│ ② 条件判断 │ ← 每次循环前判断
│ │ (i <= 10) │
│ └────────┬─────────┘
│ │
│ ┌───┴───┐
│ true false
│ │ │
│ ▼ ▼
│ ┌─────────┐ 跳出循环
│ │③ 循环体 │ (循环结束)
│ └────┬────┘
│ ▼
│ ┌──────────┐
│ │④ 条件控制 │ ← 每次循环后执行
│ │ (i++) │
│ └────┬─────┘
│ │
└─────────┘
常见的错误理解 :很多人以为i++是在循环体之前执行的,实际上条件控制在循环体之后执行 。例如第一次循环:i=1 → 判断1<=10为true → 打印数字 → i自增为2 → 再判断2<=10...
循环变量命名规范:
| 变量名 | 含义 | 使用场景 |
|---|---|---|
i, j, k |
通用循环索引 | 最常用的循环计数器,嵌套循环时外层用i,内层用j,更内层用k |
row、col |
行/列 | 二维数组或矩阵遍历时使用,语义清晰 |
count |
计数 | 统计数量时使用 |
index |
索引 | 强调位置索引 |
循环变量作用域的注意事项:
java
// ❌ 错误:在for循环内部声明的变量,循环体外无法访问
for (int i = 1; i <= 10; i++) {
System.out.println(i);
}
// System.out.println(i); // 编译错误!i在这里已经不存在了
// ✅ 正确:如果需要在循环体外使用循环变量,要在循环外部声明
int i;
for (i = 1; i <= 10; i++) {
System.out.println(i);
}
System.out.println("循环结束时i的值:" + i); // 输出11(因为最后一次i++后变为11,条件判断失败退出)
注意 :循环结束后,循环变量的值是第一个不满足条件的值 。上例中循环结束时
i=11,因为当i=11时条件i<=10为false才退出。这个特性在"查找操作"中很有用------如果循环结束后i等于数组长度,说明没找到目标。
java
// 利用循环结束后的变量值判断查找结果
int[] arr = {3, 7, 2, 9, 5};
int target = 9;
int pos;
for (pos = 0; pos < arr.length; pos++) {
if (arr[pos] == target) {
break; // 找到了就退出
}
}
// 循环结束后判断pos的值
if (pos < arr.length) {
System.out.println("找到了,位置在:" + pos);
} else {
System.out.println("没找到");
}
4.2 for循环的变体
java
// 1. 循环变量可以在外面声明
int i;
for (i = 0; i < 5; i++) {
System.out.println(i);
}
// 2. 条件控制语句可以为空(但容易死循环)
for (int j = 0; j < 5; ) {
System.out.println(j);
j++; // 在循环体内控制
}
// 3. 多个循环变量
for (int m = 0, n = 10; m < n; m++, n--) {
System.out.println("m=" + m + ", n=" + n);
}
4.3 经典for循环练习
java
// 1. 计算1到100的累加和
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
System.out.println("1+2+...+100 = " + sum); // 5050
// 2. 计算100-999之间的水仙花数
// 水仙花数:各位数字立方和等于该数本身
for (int num = 100; num <= 999; num++) {
int hundreds = num / 100;
int tens = num / 10 % 10;
int ones = num % 10;
if (hundreds * hundreds * hundreds
+ tens * tens * tens
+ ones * ones * ones == num) {
System.out.println("水仙花数:" + num);
}
}
// 3. 九九乘法表
for (int row = 1; row <= 9; row++) {
for (int col = 1; col <= row; col++) {
System.out.print(col + "×" + row + "=" + (col * row) + "\t");
}
System.out.println(); // 换行
}
循环嵌套的性能注意事项:
循环嵌套的执行次数是各层循环次数的乘积 。比如外层循环执行m次,内层循环执行n次,总执行次数是m×n次。这意味着随着嵌套层数增加,性能下降非常快:
| 循环层数 | 每层执行100次 | 每层执行1000次 |
|---|---|---|
| 双层嵌套 | 10,000次 | 1,000,000次 |
| 三层嵌套 | 1,000,000次 | 1,000,000,000次 |
| 四层嵌套 | 100,000,000次 | 极慢,几乎不可用 |
优化建议:
java
// ❌ 不好:重复计算在循环内部
for (int i = 0; i < list.size(); i++) { // 每次循环都调用size()
for (int j = 0; j < arr.length; j++) { // 每次循环都访问length
// ...
}
}
// ✅ 好:把不变的计算提到循环外部
int size = list.size(); // 只计算一次
int len = arr.length; // 只计算一次
for (int i = 0; i < size; i++) {
for (int j = 0; j < len; j++) {
// ...
}
}
java
// ❌ 不好:将不依赖循环的变量创建在循环体内
for (int i = 0; i < 10000; i++) {
String result = expensiveCalculation(); // 每次都重新计算
System.out.println(result);
}
// ✅ 好:将不变的计算提到循环体外
String result = expensiveCalculation(); // 只计算一次
for (int i = 0; i < 10000; i++) {
System.out.println(result);
}
4.4 增强型for循环(for-each)
用于遍历数组或集合,语法更简洁:
java
int[] scores = {85, 92, 78, 95, 88};
// 普通for循环
for (int i = 0; i < scores.length; i++) {
System.out.print(scores[i] + " ");
}
System.out.println();
// 增强for循环(推荐用于遍历)
for (int score : scores) {
System.out.print(score + " ");
}
System.out.println();
注意:增强for循环无法获取索引,如果需要索引还是得用普通for。
增强for循环的局限性总结:
| 限制 | 说明 | 替代方案 |
|---|---|---|
| 无法获取索引 | for (int x : arr) 不知道当前是第几个元素 |
使用普通for循环 |
| 不能修改元素值 | x = 10 只修改临时变量,不影响原数组 |
使用普通for循环 + 索引赋值 |
| 只能正向遍历 | 不能从后往前遍历 | 使用普通for循环反向遍历 |
| 遍历过程中不能删除 | 会抛出ConcurrentModificationException |
使用迭代器的remove()方法 |
java
// 演示:增强for循环不能修改原数组
int[] nums = {1, 2, 3, 4, 5};
for (int n : nums) {
n = n * 2; // 修改的是临时变量n,不影响原数组
}
// nums仍然是{1, 2, 3, 4, 5},没有被改变!
// 正确做法:使用普通for循环
for (int i = 0; i < nums.length; i++) {
nums[i] = nums[i] * 2; // 通过索引修改原数组
}
5.3 循环的选择建议
选择循环结构时,可以从以下几个维度思考:
| 场景 | 推荐使用 | 原因 |
|---|---|---|
| 明确知道循环次数 | for循环 |
初始化、条件、步进集中在一行,结构紧凑 |
| 循环次数不明确 | while循环 |
只需要一个条件表达式,更灵活 |
| 至少需要执行一次 | do-while循环 |
先执行后判断,确保循环体至少运行一次 |
| 遍历数组/集合的全部元素 | 增强for循环 |
语法简洁,不需要手动管理索引 |
| 需要索引的遍历 | 普通for循环 |
可以通过索引访问和修改元素 |
| 无限循环 + 内部退出 | while(true) |
意图明确,比for(;;)更易读 |
| 多层嵌套中跳出 | 带标签的break/continue |
一次跳出多层循环,无需额外标志位 |
快速决策流程图:
需要循环?
├── 知道次数? → for循环
├── 遍历全部元素且不需要索引? → 增强for循环
├── 至少执行一次? → do-while循环
├── 无限循环然后内部条件退出? → while(true)
└── 其他情况 → while循环
五、循环结构------while循环
5.1 while循环
java
// 语法格式
while (条件表达式) {
// 循环体
}
// 打印1到10
int i = 1;
while (i <= 10) {
System.out.print(i + " ");
i++;
}
使用场景:当循环次数不明确,只知道循环结束条件时使用while。
while(true)死循环的合理使用场景:
死循环并非一定是bug,在以下场景中是有意为之的:
java
// 场景1:服务器主循环------持续监听请求
// while (true) {
// Request req = acceptConnection(); // 阻塞等待连接
// handleRequest(req); // 处理请求
// }
// 场景2:游戏主循环------每帧更新
// while (true) {
// processInput(); // 处理玩家输入
// updateGameState(); // 更新游戏状态
// render(); // 渲染画面
// if (gameOver) break;
// }
// 场景3:命令行交互程序------等待用户输入
import java.util.Scanner;
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.println("请输入命令(输入exit退出):");
System.out.print("> ");
String command = scanner.nextLine();
if (command.equals("exit")) {
System.out.println("程序退出");
break; // 唯一的出口
}
switch (command) {
case "help":
System.out.println("可用命令:help, status, exit");
break;
case "status":
System.out.println("系统运行正常");
break;
default:
System.out.println("未知命令: " + command);
break;
}
}
scanner.close();
使用while(true)的安全守则:
- 必须设置退出条件 :循环体内必须存在
break或return语句,否则程序永不终止 - 退出条件要可达:确保退出条件的代码路径一定会被执行到
- 考虑超时机制:长时间运行的循环可以添加超时检测
- IDE环境下谨慎:在IDE中不小心启动了一个无退出的死循环,可能需要强制终止进程
5.2 do-while循环
与while的区别在于:无论条件是否满足,循环体至少执行一次。
java
// 语法格式
do {
// 循环体
} while (条件表达式);
// 示例:猜数字游戏框架
int secret = 7;
int guess;
do {
// 模拟用户输入
guess = 5; // 假设用户猜5
System.out.println("你猜的数字是:" + guess);
if (guess < secret) {
System.out.println("猜小了!");
} else if (guess > secret) {
System.out.println("猜大了!");
}
} while (guess != secret);
System.out.println("恭喜你猜对了!");
do-while的执行流程图解:
程序开始
│
▼
┌──────────┐
│ 执行循环体 │ ← 先执行(不判断条件)
└────┬─────┘
▼
┌──────────┐
│ 判断条件 │
└────┬─────┘
│
┌───┴───┐
true false
│ │
│ ▼
│ 循环结束
└──→ 回到循环体
do-while的实际应用场景:
java
// 场景1:输入校验 ------ 至少要求用户输入一次
import java.util.Scanner;
Scanner sc = new Scanner(System.in);
String password;
do {
System.out.print("请输入密码(至少6位):");
password = sc.nextLine();
} while (password.length() < 6);
System.out.println("密码设置成功!");
// 场景2:菜单循环 ------ 打印菜单后至少执行一次选择
int choice;
do {
System.out.println("===== 菜单 =====");
System.out.println("1. 开始游戏");
System.out.println("2. 设置");
System.out.println("3. 退出");
choice = 1; // 模拟用户选择
// 处理用户选择...
} while (choice != 3);
do-while常见错误:
java
// ❌ 错误:while后面忘记分号
do {
System.out.println("hello");
} while (true) // 编译错误!缺少分号
// ✅ 正确:while条件后必须有分号
do {
System.out.println("hello");
} while (true); // 注意这个分号!
// ❌ 错误:在do-while中声明的变量无法在while条件中使用
do {
int a = 10;
} while (a > 5); // 编译错误!a超出了作用域
// ✅ 正确:变量必须在do-while外部声明
int a;
do {
a = 10;
} while (a > 5);
六、循环控制关键字
6.1 break------跳出循环
java
// break用于终止当前循环
// 示例:找到第一个能被3和7同时整除的数
for (int i = 1; i <= 100; i++) {
if (i % 3 == 0 && i % 7 == 0) {
System.out.println("找到了:" + i); // 21
break; // 跳出循环
}
}
6.2 continue------跳过本次循环
continue和break的区别是常考的知识点,可以用一张表说清楚:
| 关键字 | 作用范围 | 效果 | 后续迭代 |
|---|---|---|---|
break |
终止整个循环 | 直接跳出循环体,循环结束 | 不再执行 |
continue |
跳过本次迭代 | 跳过循环体内continue后面的语句,进入下一次迭代 | 继续执行 |
执行流程对比:
break 流程: continue 流程:
循环开始 循环开始
│ │
▼ ▼
条件判断 条件判断
│ │
▼ ▼
循环体前半部分 循环体前半部分
│ │
▼ ▼
遇到 break ──→ 直接跳出循环 遇到 continue ──→ 跳到条件控制
✗ (后面不执行) │ │
✗ (后面不执行) │
▼
条件控制 → 条件判断 → ...
// continue用于跳过本次循环,继续下一次
// 示例:打印1到20中的偶数
for (int i = 1; i <= 20; i++) {
if (i % 2 != 0) {
continue; // 奇数就跳过,不执行后面的打印
}
System.out.print(i + " ");
}
// 输出:2 4 6 8 10 12 14 16 18 20
6.3 带标签的break和continue
当有多层嵌套循环时,可以使用标签精确控制跳转:
java
outer: for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
if (i == 2 && j == 2) {
System.out.println("跳出外层循环");
break outer; // 跳出外层循环
}
System.out.println("i=" + i + ", j=" + j);
}
}
System.out.println("双重循环结束");
6.4 死循环与退出
java
// 死循环:条件永远为true
while (true) {
System.out.println("这是一个死循环");
// 必须用break退出,否则程序不会停止
break;
}
// 经典的死循环写法
for (;;) {
// 某些特殊场景需要使用
break;
}
while(true)和for(;;)的区别:
两者在功能上完全等价,都可以实现无限循环。区别主要在于代码风格:
| 写法 | 字节码 | 可读性 | 推荐度 |
|---|---|---|---|
while (true) |
编译后与for(;;)相同 |
语义明确------"当true时循环" | 推荐 |
for (;;) |
编译后与while(true)相同 |
较晦涩,新手可能看不懂 | 不推荐 |
两者编译后的字节码完全相同,不存在性能差异。建议统一使用while (true),因为其意图更直观。
常见的死循环逃生手段:
java
// 手段1:break跳出
while (true) {
if (condition) {
break;
}
}
// 手段2:return结束方法
public void infiniteProcess() {
while (true) {
if (condition) {
return; // 直接结束方法
}
}
}
// 手段3:抛出异常
while (true) {
if (errorCondition) {
throw new RuntimeException("发生异常,终止循环");
}
}
// 手段4:使用标志位(更可读的方式)
boolean running = true;
while (running) {
if (condition) {
running = false; // 设置标志位,等当前迭代完成后自然退出
}
}
四种逃生手段的选择 :
break最常用;return适用于需要在方法中间退出;异常适用于错误处理场景;标志位适用于需要当前迭代完成后再退出的场景。
七、综合案例------学生成绩管理系统(简化版)
本案例综合运用了数组遍历、if-else分支判断、最大值/最小值查找、累积求和等多项流程控制技巧,是前面所有知识点的集中演练。
程序执行流程分析:
① 定义成绩数组 [89, 76, 95, ...]
│
▼
② 增强for循环遍历每个成绩
│ ├── 累加到sum(求总和)
│ ├── if比较更新max(找最高分)
│ ├── if比较更新min(找最低分)
│ └── if-else if分支统计等级人数
│
▼
③ 计算平均分 = sum / 人数
│
▼
④ 打印统计报告
│
▼
⑤ if-else判断整体情况
java
public class ScoreManagement {
public static void main(String[] args) {
// 学生成绩数据
int[] scores = {89, 76, 95, 68, 82, 78, 91, 88, 73, 95};
// 统计各等级人数
int excellent = 0, good = 0, average = 0, poor = 0;
int sum = 0, max = scores[0], min = scores[0];
for (int score : scores) {
// 累计总分
sum += score;
// 找最大值和最小值
if (score > max) max = score;
if (score < min) min = score;
// 统计等级
if (score >= 90) {
excellent++;
} else if (score >= 80) {
good++;
} else if (score >= 60) {
average++;
} else {
poor++;
}
}
double avg = (double) sum / scores.length;
System.out.println("====== 成绩统计报告 ======");
System.out.println("平均分:" + String.format("%.1f", avg));
System.out.println("最高分:" + max);
System.out.println("最低分:" + min);
System.out.println("优秀(≥90):" + excellent + "人");
System.out.println("良好(≥80):" + good + "人");
System.out.println("中等(≥60):" + average + "人");
System.out.println("不及格(<60):" + poor + "人");
// 判断整体情况
if (avg >= 80 && poor == 0) {
System.out.println("整体情况:优秀!");
} else if (avg >= 60) {
System.out.println("整体情况:良好");
} else {
System.out.println("整体情况:需要加强");
}
}
}
总结
本文详细讲解了Java流程控制的三大结构:
- 顺序结构:代码按书写顺序从上到下执行
- 选择结构 :
if/if-else/if-else if-else用于范围判断,switch用于等值判断,注意break防穿透 - 循环结构 :
for适合次数明确的循环,while适合条件控制,do-while至少执行一次,增强for用于遍历 - 控制关键字 :
break终止循环,continue跳过本次,标签可控制多层循环
流程控制是编程的核心逻辑,熟练掌握这些语法后,你就可以写出有"智能"的程序了。下一篇文章我们将学习Java数组的使用。
综合练习题(纸笔推理)
以下练习题不需要写代码,请用纸笔推导出结果,检验你对流程控制的理解程度。
题目一:if-else条件覆盖
阅读以下代码,回答:分别输入score=85、score=72、score=58时,输出分别是什么?
输入 score 的值
if score >= 90:
输出 "A"
else if score >= 80:
输出 "B"
else if score >= 70:
输出 "C"
else if score >= 60:
输出 "D"
else:
输出 "E"
点击查看答案
score=85:输出 B(85>=80为true,进入第二个分支,不再判断后续条件)score=72:输出 C(72>=70为true,进入第三个分支)score=58:输出 E(所有条件都不满足,进入else)
题目二:switch穿透推理
以下代码执行后,result的值是多少?
java
int x = 2;
int result = 0;
switch (x) {
case 1:
result += 10;
case 2:
result += 20;
case 3:
result += 30;
break;
case 4:
result += 40;
default:
result += 100;
}
点击查看答案
result = 50
执行过程:
- 匹配
case 2,执行result += 20,result变为20,但没有break,继续穿透 - 执行
case 3,执行result += 30,result变为50,遇到break退出 - 不会执行
case 4和default
题目三:嵌套循环计数
下面代码执行完毕后,count的值是多少?
java
int count = 0;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
if (j % 2 == 0) {
count++;
}
}
}
点击查看答案
count = 6
解析:外层循环3次(i=0,1,2),内层循环4次(j=0,1,2,3),内层中j为偶数的情况是j=0和j=2,每轮内层循环count++执行2次。总共3×2=6次。
题目四:变量作用域
以下代码是否能通过编译?如果能,输出是什么?如果不能,错在哪里?
java
for (int i = 0; i < 5; i++) {
System.out.print(i + " ");
}
System.out.println("\n循环结束后的i:" + i);
点击查看答案
不能通过编译。
原因:循环变量i在for语句的初始化部分声明,其作用域仅限于for循环体内。循环结束后i已经超出作用域,编译器报错"找不到符号 i"。
修正方法:将i的声明移到for循环外部。
题目五:break与continue区分
以下代码执行后,输出的内容是什么?
java
for (int i = 1; i <= 10; i++) {
if (i == 3) {
continue;
}
if (i == 7) {
break;
}
System.out.print(i + " ");
}
点击查看答案
输出:1 2 4 5 6
执行过程:
i=1:不满足任何if,打印1i=2:不满足任何if,打印2i=3:满足i==3,执行continue,跳过本次循环后面的所有语句,不打印3i=4,5,6:不满足任何if,打印4 5 6i=7:满足i==7,执行break,直接跳出整个循环,不再执行后续迭代
注意:continue只跳过当前迭代,循环继续;break终止整个循环。
题目六:do-while与while的区别
以下两段代码执行后,各自的输出分别是什么?
java
// 代码A
int a = 10;
while (a < 5) {
System.out.print("A ");
a++;
}
System.out.println("结束");
// 代码B
int b = 10;
do {
System.out.print("B ");
b++;
} while (b < 5);
System.out.println("结束");
点击查看答案
- 代码A 输出:
结束(因为条件10<5一开始就为false,循环体一次都不执行) - 代码B 输出:
B 结束(do-while先执行循环体,打印一次"B ",然后判断11<5为false退出)
这就是while和do-while的核心区别:do-while至少执行一次。
题目七:闰年条件复合判断
下列代码判断年份是否为闰年。请问year=1900和year=2000分别输出什么?
java
int year = ?; // 分别代入1900和2000
if (year % 400 == 0) {
System.out.println("闰年");
} else if (year % 100 == 0) {
System.out.println("平年");
} else if (year % 4 == 0) {
System.out.println("闰年");
} else {
System.out.println("平年");
}
点击查看答案
year=1900:1900%400=300不成立 → 1900%100=0成立 → 输出**"平年"**year=2000:2000%400=0成立 → 输出**"闰年"**
关键点:条件顺序非常重要。如果先判断%4再判断%100和%400,1900年会被误判为闰年。
题目八:标签break的跳转范围
下列代码会打印多少条System.out.println输出?
java
outer:
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
if (i * j > 4) {
break outer;
}
System.out.println("i=" + i + ", j=" + j);
}
}
点击查看答案
输出4条记录:
i=1, j=1
i=1, j=2
i=1, j=3
i=2, j=1
执行过程:
i=1, j=1:1*1=1≤4,打印i=1, j=2:1*2=2≤4,打印i=1, j=3:1*3=3≤4,打印i=2, j=1:2*1=2≤4,打印i=2, j=2:2*2=4≤4,打印 → 等等,4不大于4,继续打印i=2, j=3:2*3=6>4,触发break outer,跳出外层循环,程序结束
正确答案:5条,i=2,j=2也会打印。
✅ 亮点总结
- if与switch的选择对比 :
if-else适合范围判断(如成绩等级),switch适合等值匹配(如星期几),各有最佳使用场景 - switch穿透机制详解 :
break缺失导致的"穿透效应"既有风险也有妙用,多个case合并时故意省略break更简洁 - 三种循环的本质区别 :
for已知循环次数、while条件驱动、do-while先执行后判断,各自适用不同场景 - 标签控制多层循环 :带标签的
break和continue可以一次性跳出多层嵌套循环,避免设置额外标志位 - 成绩统计综合案例:结合数组遍历、if-else分支、最大值查找等多知识点,完整呈现流程控制在数据处理中的应用
适用场景
- 实现业务规则引擎,如根据订单金额、用户等级计算折扣,大量使用
if-else if分支判断 - 处理批量数据统计,如遍历学生成绩数组同时计算最高分、最低分、平均分和各等级人数
- 编写菜单驱动的控制台程序,外层while死循环配合内层switch实现交互式功能选择
扩展方向
- 数组操作 :学完流程控制后处理批量数据,推荐阅读 04_Java数组操作全解
- 递归算法:用递归替代循环解决汉诺塔、斐波那契数列等问题,理解递归与迭代的优劣
- Stream函数式编程 :在后续学习中用
filter、map、reduce等链式操作替代传统循环,代码更简洁