03_Java流程控制详解

Java流程控制详解:掌握if、switch、for和while循环

文章目录

前言

在程序的世界中,代码通常不是按顺序一行行执行的。我们经常需要根据条件做出不同选择,或者重复执行某段代码。这就是流程控制要做的事情。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)虽然语法上可能通过编译(如果flagboolean类型),但逻辑上是一个常见错误------本意是比较,实际是赋值后取赋值结果。
  • 浮点数不建议直接用于等值判断 ,因为浮点数计算存在精度误差。例如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属于第一个ifa > 3),但实际上编译器将它绑定到了最近的ifb > 20)。因为a > 3true,进入内层if;但b > 20false,所以匹配的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-elseswitch都可以,差异不大
  • 3~10个等值分支 :优先使用switch,代码更整洁,且编译器可能优化为跳转表提升性能
  • 涉及范围判断 :只能用if-elseswitch不支持范围匹配(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支持的表达式类型

  • byteshortintchar
  • 对应的包装类:ByteShortIntegerCharacter
  • 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.MONDAY1更有语义意义
  • 编译期检查 :如果你缺少某个枚举值的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);

箭头语法的优势

  1. 无需break:每个分支只执行自己的语句,不会穿透到下一个case
  2. 支持多值合并case 1, 2, 3, 4, 5 -> 一行搞定多个值
  3. 可作为表达式:switch可以返回值赋给变量(如上例)
  4. 编译器强制覆盖:如果遗漏某个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
rowcol 行/列 二维数组或矩阵遍历时使用,语义清晰
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)的安全守则

  1. 必须设置退出条件 :循环体内必须存在breakreturn语句,否则程序永不终止
  2. 退出条件要可达:确保退出条件的代码路径一定会被执行到
  3. 考虑超时机制:长时间运行的循环可以添加超时检测
  4. 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------跳过本次循环

continuebreak的区别是常考的知识点,可以用一张表说清楚:

关键字 作用范围 效果 后续迭代
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流程控制的三大结构:

  1. 顺序结构:代码按书写顺序从上到下执行
  2. 选择结构if/if-else/if-else if-else用于范围判断,switch用于等值判断,注意break防穿透
  3. 循环结构for适合次数明确的循环,while适合条件控制,do-while至少执行一次,增强for用于遍历
  4. 控制关键字break终止循环,continue跳过本次,标签可控制多层循环

流程控制是编程的核心逻辑,熟练掌握这些语法后,你就可以写出有"智能"的程序了。下一篇文章我们将学习Java数组的使用。

综合练习题(纸笔推理)

以下练习题不需要写代码,请用纸笔推导出结果,检验你对流程控制的理解程度。

题目一:if-else条件覆盖

阅读以下代码,回答:分别输入score=85score=72score=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

执行过程:

  1. 匹配case 2,执行result += 20,result变为20,但没有break,继续穿透
  2. 执行case 3,执行result += 30,result变为50,遇到break退出
  3. 不会执行case 4default

题目三:嵌套循环计数

下面代码执行完毕后,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);

点击查看答案

不能通过编译

原因:循环变量ifor语句的初始化部分声明,其作用域仅限于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,打印1
  • i=2:不满足任何if,打印2
  • i=3:满足i==3,执行continue跳过本次循环后面的所有语句,不打印3
  • i=4,5,6:不满足任何if,打印4 5 6
  • i=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退出)

这就是whiledo-while的核心区别:do-while至少执行一次

题目七:闰年条件复合判断

下列代码判断年份是否为闰年。请问year=1900year=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先执行后判断,各自适用不同场景
  • 标签控制多层循环 :带标签的breakcontinue可以一次性跳出多层嵌套循环,避免设置额外标志位
  • 成绩统计综合案例:结合数组遍历、if-else分支、最大值查找等多知识点,完整呈现流程控制在数据处理中的应用

适用场景

  • 实现业务规则引擎,如根据订单金额、用户等级计算折扣,大量使用if-else if分支判断
  • 处理批量数据统计,如遍历学生成绩数组同时计算最高分、最低分、平均分和各等级人数
  • 编写菜单驱动的控制台程序,外层while死循环配合内层switch实现交互式功能选择

扩展方向

  • 数组操作 :学完流程控制后处理批量数据,推荐阅读 04_Java数组操作全解
  • 递归算法:用递归替代循环解决汉诺塔、斐波那契数列等问题,理解递归与迭代的优劣
  • Stream函数式编程 :在后续学习中用filtermapreduce等链式操作替代传统循环,代码更简洁
相关推荐
霍格沃兹测试学院-小舟畅学1 小时前
接口自动化测试的下一个十年:从脚本到Skills,让AI学会“如何测”
java·前端·人工智能
SoftLipaRZC1 小时前
C语言内存函数完全指南:memcpy/memmove/memset/memcmp
c语言·开发语言
2201_761199041 小时前
python运维1
运维·开发语言·python
我命由我123452 小时前
Retrofit - URL 格式错误问题、支持 HTTP 与 HTTPS
java·http·https·java-ee·android studio·android-studio·retrofit
盼小辉丶2 小时前
PyTorch深度学习实战(55)——在Android上部署PyTorch模型
android·pytorch·python·模型部署
灰灰老师2 小时前
Docker部署Tomcat9
java·linux·docker·tomcat
Cx330❀2 小时前
【Qt 核心机制篇】深度解析 Qt 信号与槽(Signals & Slots)机制:从底层原理、实战演练到 Lambda 进阶
linux·开发语言·c++·人工智能·qt·ubuntu
SunnyDays10112 小时前
使用 Python 加密、保护和签名 PowerPoint 演示文稿 (PPT)
python·powerpoint·加密 ppt·保护 ppt·给ppt添加数字签名