编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • C语言入门精通

  • Cpp入门到精通

  • Java入门精通

    • README
    • 入门教程

      • README
      • 基础语法
      • 数据类型
      • 运算符
      • 字符串和数组
      • 流程语句
        • 5.1 程序流程结构
          • 5.1.1 综合案例:流程结构演示器
          • 5.1.2 训练题
        • 5.2 选择结构
          • 5.2.1 if语句
          • 5.2.2 单行格式if语句
          • 5.2.3 if-else语句
          • 5.2.4 多条件的if语句
          • 5.2.5 嵌套if语句
          • 5.2.6 三元运算符
          • 5.2.7 switch语句
          • 5.2.8 switch增强写法(JDK14+)
          • 5.2.9 选择结构底层原理
          • 5.2.10 综合案例:智能日期查询器
          • 5.2.11 选择结构训练题
        • 5.3 循环结构
          • 5.3.1 while循环语句
          • 5.3.2 do...while循环语句
          • 5.3.3 for循环语句
          • 5.3.4 增强for循环
          • 5.3.5 嵌套循环
          • 5.3.6 循环结构底层原理
          • 5.3.7 综合案例:图案打印器
          • 5.3.8 循环结构训练题
        • 5.4 跳转语句
          • 5.4.1 break语句
          • 5.4.2 continue语句
          • 5.4.3 标签和break/continue
          • 5.4.4 跳转语句底层原理
          • 5.4.5 综合案例:素数查找器
          • 5.4.6 跳转语句训练题
      • 函数方法
      • 类和对象
      • 继承和多态
      • 接口和抽象类
      • 异常处理
      • 集合框架
      • IO流和File
      • 线程和锁
      • 泛型
      • 注解和反射
    • 综合案例

    • 专栏博客

  • Go入门到精通

  • JavaScript入门

  • CodeX
  • Java入门精通
  • 入门教程
杨充
2026-04-07
目录

流程语句

# 05.流程语句

# 目录介绍

  • 5.1 程序流程结构
    • 5.1.1 综合案例:流程结构演示器
    • 5.1.2 训练题
  • 5.2 选择结构
    • 5.2.1 if语句
    • 5.2.2 单行格式if语句
    • 5.2.3 if-else语句
    • 5.2.4 多条件的if语句
    • 5.2.5 嵌套if语句
    • 5.2.6 三元运算符
    • 5.2.7 switch语句
    • 5.2.8 switch增强写法(JDK14+)
    • 5.2.9 选择结构底层原理
    • 5.2.10 综合案例:智能日期查询器
    • 5.2.11 选择结构训练题
  • 5.3 循环结构
    • 5.3.1 while循环语句
    • 5.3.2 do...while循环语句
    • 5.3.3 for循环语句
    • 5.3.4 增强for循环
    • 5.3.5 嵌套循环
    • 5.3.6 循环结构底层原理
    • 5.3.7 综合案例:图案打印器
    • 5.3.8 循环结构训练题
  • 5.4 跳转语句
    • 5.4.1 break语句
    • 5.4.2 continue语句
    • 5.4.3 标签和break/continue
    • 5.4.4 跳转语句底层原理
    • 5.4.5 综合案例:素数查找器
    • 5.4.6 跳转语句训练题

# 5.1 程序流程结构

Java 支持最基本的三种程序运行结构:顺序结构、选择结构、循环结构

  • 顺序结构:程序按顺序执行,不发生跳转
  • 选择结构:依据条件是否满足,有选择的执行相应功能
  • 循环结构:依据条件是否满足,循环多次执行某段代码

为什么需要流程控制? 程序设计的本质是处理各种条件和重复操作。假设没有流程控制,程序只能自上而下顺序执行,无法根据不同输入做出不同响应,也无法处理重复的任务。流程控制是将"人的思考逻辑"映射为"机器执行逻辑"的基石。

程序流程的本质:从 CPU 的角度看,所有的流程控制最终都会转化为跳转指令。顺序结构对应 PC(程序计数器)自然递增;选择结构对应条件跳转指令(如 if_icmpge);循环结构对应无条件跳转 + 条件跳转的组合(先执行循环体,再跳回判断条件)。理解这个本质,能帮助你更好地理解编译器和 JVM 的工作原理。

# 5.1.1 综合案例:流程结构演示器

编写一个程序,用简单的例子演示顺序、选择、循环三种基本流程结构。

public class FlowStructureDemo {
    public static void main(String[] args) {
        // 顺序结构:自上而下执行
        System.out.println("===== 顺序结构 =====");
        System.out.println("步骤1: 起床");
        System.out.println("步骤2: 洗漱");
        System.out.println("步骤3: 吃早餐");
        System.out.println("步骤4: 出门");

        // 选择结构:根据条件执行不同分支
        System.out.println("\n===== 选择结构 =====");
        int hour = 14;
        if (hour < 12) {
            System.out.println("现在是上午" + hour + "点,说:上午好!");
        } else if (hour < 18) {
            System.out.println("现在是下午" + (hour - 12) + "点,说:下午好!");
        } else {
            System.out.println("现在是晚上,说:晚上好!");
        }

        // 循环结构:重复执行
        System.out.println("\n===== 循环结构 =====");
        System.out.println("倒计时开始:");
        for (int i = 5; i >= 1; i--) {
            System.out.println("  " + i + "...");
        }
        System.out.println("  发射!");

        // 三种结构的组合
        System.out.println("\n===== 组合应用:找出1~20内的偶数 =====");
        int count = 0;                    // 顺序
        for (int i = 1; i <= 20; i++) {   // 循环
            if (i % 2 == 0) {             // 选择
                System.out.print(i + " ");
                count++;
            }
        }
        System.out.println("\n共" + count + "个偶数");  // 顺序
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

# 5.1.2 训练题

  1. 选择题:以下关于程序流程结构的说法,正确的是:

    • A. Java 只支持顺序结构和循环结构
    • B. 所有流程控制在 CPU 层面最终转化为跳转指令
    • C. 选择结构只能通过 if-else 实现
    • D. 循环结构至少执行一次循环体
  2. 填空题:从 CPU 角度看,顺序结构对应 ___(程序计数器)自然递增;选择结构对应 ___ 跳转指令;循环结构对应 ___ 跳转和 ___ 跳转的组合。

  3. 思考题:有人说"任何程序逻辑都可以只用顺序、选择、循环三种结构来表达"(结构化程序定理)。请思考:递归算法是否可以用这三种结构改写?如果可以,改写后有什么优缺点?

# 5.2 选择结构

# 5.2.1 if语句

作用:执行满足条件的语句

if 语句的三种形式:单行格式 if、if-else、多条件 if-else if-else。

注意:Java 的 if 条件必须是 boolean 类型,不能像 C++ 那样用整数作为条件。

# 5.2.2 单行格式if语句

int score = 85;
if (score >= 60) {
    System.out.println("及格");
}
1
2
3
4

# 5.2.3 if-else语句

int score = 55;
if (score >= 60) {
    System.out.println("及格");
} else {
    System.out.println("不及格");
}
1
2
3
4
5
6

# 5.2.4 多条件的if语句

int score = 85;
if (score >= 90) {
    System.out.println("优秀");
} else if (score >= 80) {
    System.out.println("良好");
} else if (score >= 60) {
    System.out.println("及格");
} else {
    System.out.println("不及格");
}
1
2
3
4
5
6
7
8
9
10

# 5.2.5 嵌套if语句

int age = 20;
boolean hasLicense = true;

if (age >= 18) {
    if (hasLicense) {
        System.out.println("可以开车");
    } else {
        System.out.println("年龄够了,但没有驾照");
    }
} else {
    System.out.println("未满18岁,不能开车");
}
1
2
3
4
5
6
7
8
9
10
11
12

# 5.2.6 三元运算符

int a = 10, b = 20;
int max = (a > b) ? a : b;
System.out.println("较大值:" + max);  // 20
1
2
3

# 5.2.7 switch语句

int day = 3;
switch (day) {
    case 1:
        System.out.println("星期一");
        break;
    case 2:
        System.out.println("星期二");
        break;
    case 3:
        System.out.println("星期三");
        break;
    default:
        System.out.println("其他");
        break;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Java 的 switch 支持的类型:byte、short、int、char、String(JDK 7+)、枚举。

对比 C++:C++ 的 switch 不支持 String 类型,只支持整型和枚举。

// JDK 7+ 支持 String
String fruit = "苹果";
switch (fruit) {
    case "苹果":
        System.out.println("Apple");
        break;
    case "香蕉":
        System.out.println("Banana");
        break;
    default:
        System.out.println("Unknown");
}
1
2
3
4
5
6
7
8
9
10
11
12

疑惑:switch 是如何支持 String 类型的?

在 JDK 7 之前,switch 只支持 int、byte、short、char 四种整数类型和枚举。JDK 7 为什么突然就能支持 String 了?底层实现有什么不同吗?

答疑:switch 对 String 的支持是编译器层面的语法糖,JVM 本身并没有新增 switch 字符串的字节码指令。

论证:编译器将 switch(String) 转换为两步操作。第一步:先对字符串的 hashCode() 做 switch(整数比较),找到候选分支。第二步:再用 equals() 方法做精确匹配(因为不同字符串可能有相同的 hashCode)。反编译字节码可以看到:

// 编译器转换后的等价代码(伪代码)
String fruit = "苹果";
int hash = fruit.hashCode();
switch (hash) {
    case 33529:   // "苹果".hashCode() 的值
        if (fruit.equals("苹果")) {
            System.out.println("Apple");
        }
        break;
    case 39321:   // "香蕉".hashCode() 的值
        if (fruit.equals("香蕉")) {
            System.out.println("Banana");
        }
        break;
    default:
        System.out.println("Unknown");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

结果展示:所以 String 类型的 switch 本质上还是整数比较,这也是为什么 case 标签中的字符串不能为 null(null.hashCode() 会抛 NPE)。理解这个原理后,你就能明白 switch 的性能特点:对 String 的 switch 比多个 if-else 快,因为先用 hashCode 做整数跳转,再用 equals 精确匹配。

# 5.2.8 switch增强写法(JDK14+)

JDK 14 引入了 switch 表达式,更加简洁:

int day = 3;
String dayName = switch (day) {
    case 1 -> "星期一";
    case 2 -> "星期二";
    case 3 -> "星期三";
    case 4 -> "星期四";
    case 5 -> "星期五";
    case 6, 7 -> "周末";
    default -> "无效";
};
System.out.println(dayName);  // 星期三
1
2
3
4
5
6
7
8
9
10
11

switch 表达式 vs 传统 switch 语句的关键区别:

对比项 传统 switch 语句 switch 表达式(JDK14+)
是否有返回值 没有,是语句 有,是表达式
是否需要 break 需要,否则 fall-through 不需要,-> 自动结束
多值匹配 多个 case 贯穿 case 6, 7 ->
穷举检查 不检查 必须穷举所有可能
yield 关键字 不支持 在代码块中返回值
// yield 的使用:在代码块中返回值
int numLetters = switch (day) {
    case 1, 3, 5 -> {
        System.out.println("工作日(奇数)");
        yield 3;  // 使用 yield 返回值
    }
    case 2, 4 -> {
        System.out.println("工作日(偶数)");
        yield 2;
    }
    default -> 0;
};
1
2
3
4
5
6
7
8
9
10
11
12

JDK 17+ 的 switch 模式匹配(预览特性):

// JDK 17+ 模式匹配 switch
static String format(Object obj) {
    return switch (obj) {
        case Integer i -> "整数:" + i;
        case String s  -> "字符串:" + s;
        case null      -> "空值";
        default        -> "其他:" + obj;
    };
}
1
2
3
4
5
6
7
8
9

# 5.2.9 选择结构底层原理

if-else 的字节码实现:if-else 在字节码层面使用条件跳转指令。例如 if (a > b) 编译为 if_icmple(如果 a <= b 就跳转到 else 分支)。JVM 中常见的条件跳转指令有:

指令 含义
ifeq 等于0时跳转
ifne 不等于0时跳转
if_icmpgt 大于时跳转
if_icmplt 小于时跳转
if_icmpge 大于等于时跳转
if_icmple 小于等于时跳转

switch 的字节码实现:编译器会根据 case 值的分布选择不同的字节码指令:

  1. tableswitch:当 case 值连续或接近连续时使用。内部维护一个跳转表(数组),通过索引直接跳转,时间复杂度 O(1)。例如 case 1, 2, 3, 4, 5 会使用 tableswitch。
  2. lookupswitch:当 case 值稀疏时使用。内部维护一个有序键值对表,通过二分查找定位,时间复杂度 O(log n)。例如 case 1, 100, 1000 会使用 lookupswitch。
// tableswitch 字节码示例(case 连续)
tableswitch 1 to 5
    1: goto Label1
    2: goto Label2
    3: goto Label3
    4: goto Label4
    5: goto Label5
    default: goto LabelDefault

// lookupswitch 字节码示例(case 稀疏)
lookupswitch
    1:    goto Label1
    100:  goto Label2
    1000: goto Label3
    default: goto LabelDefault
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

这解释了为什么 switch 通常比多层 if-else 更高效:当 case 连续时,switch 的 tableswitch 实现是 O(1) 的直接跳转,而 if-else 链需要逐个比较,是 O(n) 的。

# 5.2.10 综合案例:智能日期查询器

编写一个程序,综合运用 if-else、switch 和三元运算符,根据输入的年份和月份输出该月天数、季节和工作日信息。

import java.util.Scanner;

public class DateQuery {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.print("请输入年份:");
        int year = sc.nextInt();
        System.out.print("请输入月份(1-12):");
        int month = sc.nextInt();

        if (month < 1 || month > 12) {
            System.out.println("月份无效!");
            return;
        }

        // 判断闰年(嵌套if)
        boolean isLeap = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
        System.out.println(year + "年是" + (isLeap ? "闰年" : "平年"));

        // switch表达式计算天数(JDK14+)
        int days = switch (month) {
            case 1, 3, 5, 7, 8, 10, 12 -> 31;
            case 4, 6, 9, 11 -> 30;
            case 2 -> isLeap ? 29 : 28;
            default -> 0;
        };
        System.out.println(year + "年" + month + "月有" + days + "天");

        // if-else判断季节
        String season;
        if (month >= 3 && month <= 5) {
            season = "春季";
        } else if (month >= 6 && month <= 8) {
            season = "夏季";
        } else if (month >= 9 && month <= 11) {
            season = "秋季";
        } else {
            season = "冬季";
        }
        System.out.println("季节: " + season);

        // 输出该月日历
        System.out.println("\n===== " + year + "年" + month + "月日历 =====");
        System.out.println("日  一  二  三  四  五  六");

        // 简化计算该月1号是星期几(蔡勒公式简化版)
        int y = year, m = month;
        if (m <= 2) { m += 12; y--; }
        int weekDay = (1 + 2 * m + 3 * (m + 1) / 5 + y + y / 4 - y / 100 + y / 400) % 7;
        // 0=Monday, ..., 6=Sunday; 转换为0=Sunday
        weekDay = (weekDay + 1) % 7;

        for (int i = 0; i < weekDay; i++) System.out.print("    ");
        for (int d = 1; d <= days; d++) {
            System.out.printf("%2d  ", d);
            if ((d + weekDay) % 7 == 0) System.out.println();
        }
        System.out.println();

        sc.close();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

# 5.2.11 选择结构训练题

训练1:写一个方法 String getGrade(int score),使用 if-else 将分数转换为等级:A(90-100)、B(80-89)、C(70-79)、D(60-69)、F(0-59),需要处理非法输入(小于0或大于100)。

训练2:使用 switch 表达式(JDK14+)实现一个简单计算器,输入两个数和运算符(+、-、*、/),输出计算结果。需要处理除零的情况。

训练3:判断一个年份是否为闰年。闰年规则:能被4整除但不能被100整除,或者能被400整除。请分别用 if-else 嵌套和三元运算符两种方式实现。

思考:当 if-else 的分支超过5个时,改写成 switch 一定更好吗?什么情况下 if-else 比 switch 更合适?

# 5.3 循环结构

# 5.3.1 while循环语句

while 循环在条件为 true 时重复执行循环体。先判断后执行,条件一开始就不满足则一次也不执行。

int i = 0;
while (i < 5) {
    System.out.println("i = " + i);
    i++;
}
1
2
3
4
5

while 的常见使用场景:当循环次数不确定,需要根据某个条件来结束循环时,优先选择 while。

// 实际场景:读取用户输入直到输入 "quit"
Scanner sc = new Scanner(System.in);
String input = "";
while (!input.equals("quit")) {
    System.out.print("请输入命令(输入quit退出):");
    input = sc.nextLine();
    System.out.println("你输入了:" + input);
}
1
2
3
4
5
6
7
8

# 5.3.2 do...while循环语句

do-while 循环与 while 的区别是:先执行后判断,循环体至少执行一次。

int i = 0;
do {
    System.out.println("i = " + i);
    i++;
} while (i < 5);
1
2
3
4
5

do-while 至少执行一次循环体,这在需要"先做一次再判断是否继续"的场景下非常有用。

疑惑:do-while 的使用场景是什么?似乎很少见到它。

很多初学者觉得 do-while 很鸡肋,while 就够了。实际开发中 do-while 确实使用较少,但在特定场景下是最自然的选择。

答疑:do-while 最典型的使用场景有两个:(1)用户输入验证——先让用户输入一次,再判断输入是否有效;(2)菜单系统——先显示菜单让用户操作,再决定是否继续。

论证:

// 场景1:输入验证——必须至少让用户输入一次
int number;
do {
    System.out.print("请输入1-10之间的数字:");
    number = sc.nextInt();
    if (number < 1 || number > 10) {
        System.out.println("输入无效,请重新输入!");
    }
} while (number < 1 || number > 10);

// 如果用 while 写,需要先赋一个无效的初始值或复制代码
int number2 = -1;  // 需要一个"不满足条件"的初始值
while (number2 < 1 || number2 > 10) {
    System.out.print("请输入1-10之间的数字:");
    number2 = sc.nextInt();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

结果展示:对比可以看出,do-while 版本语义更清晰:先执行操作,再判断条件。while 版本需要一个"技巧性"的初始值 -1,可读性较差。在 JDK 源码中,ThreadPoolExecutor.runWorker() 就使用了 do-while 来处理任务循环。

# 5.3.3 for循环语句

for 循环是最常用的循环结构,当循环次数已知或可以确定时,优先使用 for 循环。

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

for 循环的三个部分可以省略:

// 省略初始化
int i = 0;
for (; i < 5; i++) {
    System.out.println(i);
}

// 省略条件(死循环)
for (int j = 0; ; j++) {
    if (j >= 5) break;
    System.out.println(j);
}

// 全部省略(死循环)
for (;;) {
    // 等价于 while(true)
    break;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

for 循环执行顺序:初始化 → 条件判断 → 循环体 → 迭代语句 → 条件判断 → 循环体 → 迭代语句 → ... → 条件不满足 → 退出。

// 执行顺序验证
for (int k = 0; k < 3; k++) {
    System.out.println("k = " + k);
}
// 执行流程:
// 1. k=0(初始化,只执行一次)
// 2. 0<3? true → 输出 k=0 → k++(k变为1)
// 3. 1<3? true → 输出 k=1 → k++(k变为2)
// 4. 2<3? true → 输出 k=2 → k++(k变为3)
// 5. 3<3? false → 退出循环
1
2
3
4
5
6
7
8
9
10

# 5.3.4 增强for循环

Java 5 引入的 for-each 循环,简化数组和集合的遍历。

增强 for 循环的底层原理:for-each 是编译器的语法糖。对于数组,编译后变成普通的索引 for 循环;对于实现了 Iterable 接口的集合,编译后变成 Iterator 迭代器模式。这也解释了为什么 for-each 中不能修改集合大小(会抛 ConcurrentModificationException),因为底层是迭代器在工作。

int[] arr = {10, 20, 30, 40, 50};
for (int num : arr) {
    System.out.println(num);
}

String[] names = {"张三", "李四", "王五"};
for (String name : names) {
    System.out.println(name);
}
1
2
3
4
5
6
7
8
9

编译器转换的等价代码:

// for-each 遍历数组:编译后等价于
int[] arr = {10, 20, 30, 40, 50};
for (int i = 0; i < arr.length; i++) {  // 转换为索引遍历
    int num = arr[i];
    System.out.println(num);
}

// for-each 遍历集合:编译后等价于
List<String> list = List.of("A", "B", "C");
Iterator<String> it = list.iterator();  // 转换为迭代器
while (it.hasNext()) {
    String s = it.next();
    System.out.println(s);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

for-each 的限制:

  1. 无法获取索引:如果需要索引,必须使用传统 for 循环。
  2. 无法修改集合:在 for-each 中调用 list.add() 或 list.remove() 会触发 ConcurrentModificationException。
  3. 无法反向遍历:只能正向遍历。
  4. 无法遍历多个集合:不能同时遍历两个集合。

对比 C++:C++11 也引入了类似的范围 for 循环 for (auto x : arr)。但 C++ 可以用 for (auto& x : arr) 引用方式直接修改元素,Java 不行。

# 5.3.5 嵌套循环

// 打印九九乘法表
for (int i = 1; i <= 9; i++) {
    for (int j = 1; j <= i; j++) {
        System.out.print(j + "×" + i + "=" + (i * j) + "\t");
    }
    System.out.println();
}
1
2
3
4
5
6
7

嵌套循环的性能考虑:嵌套循环的时间复杂度是 O(n×m),三层嵌套是 O(n×m×k)。在实际开发中要特别注意嵌套循环带来的性能问题。一般建议嵌套不超过3层,超过3层应该考虑重构。

// 经典面试题:打印空心正方形
int n = 5;
for (int i = 0; i < n; i++) {
    for (int j = 0; j < n; j++) {
        // 第一行、最后一行、第一列、最后一列打印 *
        if (i == 0 || i == n - 1 || j == 0 || j == n - 1) {
            System.out.print("* ");
        } else {
            System.out.print("  ");
        }
    }
    System.out.println();
}
// 输出:
// * * * * *
// *       *
// *       *
// *       *
// * * * * *
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 5.3.6 循环结构底层原理

循环在字节码层面的实现:所有循环在编译后都会变成条件跳转 + 无条件跳转的组合。

// Java 源码
int sum = 0;
for (int i = 0; i < 10; i++) {
    sum += i;
}
1
2
3
4
5

对应的字节码(简化版):

0: iconst_0          // sum = 0
1: istore_1          // 存储 sum
2: iconst_0          // i = 0
3: istore_2          // 存储 i
4: iload_2           // 加载 i
5: bipush 10         // 加载 10
7: if_icmpge 20      // if (i >= 10) goto 20(跳出循环)
10: iload_1          // 加载 sum
11: iload_2          // 加载 i
12: iadd             // sum + i
13: istore_1         // 存储 sum
14: iinc 2, 1        // i++
17: goto 4           // 跳回第4行(条件判断)
20: ...              // 循环结束
1
2
3
4
5
6
7
8
9
10
11
12
13
14

关键点:第7行 if_icmpge 是条件跳转(不满足条件时退出),第17行 goto 是无条件跳转(跳回循环头部)。这就是循环的本质。

JIT 编译器对循环的优化:

  1. 循环展开(Loop Unrolling):JIT 可能将小循环展开为连续语句,减少跳转次数。
  2. 循环不变量外提(Loop-Invariant Code Motion):将循环中不变的计算移到循环外面。
  3. OSR 编译(On-Stack Replacement):当某个循环被执行很多次后,JIT 会在循环执行过程中将其编译为本地代码,不需要等到方法退出。
// 循环不变量外提示例
for (int i = 0; i < list.size(); i++) {  // list.size() 每次都调用
    // ...
}
// JIT 优化后等价于:
int size = list.size();  // 提到循环外面(如果 JIT 能确定 list 不变)
for (int i = 0; i < size; i++) {
    // ...
}
1
2
3
4
5
6
7
8
9

# 5.3.7 综合案例:图案打印器

编写一个程序,使用 while、for、do-while 和嵌套循环打印各种图案,展示循环结构的灵活运用。

public class PatternPrinter {
    public static void main(String[] args) {
        int n = 5;

        // for循环:直角三角形
        System.out.println("===== 直角三角形(for) =====");
        for (int i = 1; i <= n; i++) {
            for (int j = 0; j < i; j++) {
                System.out.print("* ");
            }
            System.out.println();
        }

        // while循环:等腰三角形
        System.out.println("\n===== 等腰三角形(while) =====");
        int row = 1;
        while (row <= n) {
            int spaces = n - row;
            int stars = 2 * row - 1;
            while (spaces-- > 0) System.out.print(" ");
            int s = 0;
            while (s++ < stars) System.out.print("*");
            System.out.println();
            row++;
        }

        // for循环:空心正方形
        System.out.println("\n===== 空心正方形(for) =====");
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (i == 0 || i == n - 1 || j == 0 || j == n - 1) {
                    System.out.print("* ");
                } else {
                    System.out.print("  ");
                }
            }
            System.out.println();
        }

        // 嵌套循环:九九乘法表
        System.out.println("\n===== 九九乘法表 =====");
        for (int i = 1; i <= 9; i++) {
            for (int j = 1; j <= i; j++) {
                System.out.printf("%d×%d=%-3d", j, i, i * j);
            }
            System.out.println();
        }

        // do-while:数字金字塔
        System.out.println("\n===== 数字金字塔(do-while) =====");
        int level = 1;
        do {
            for (int sp = 0; sp < n - level; sp++) System.out.print(" ");
            for (int j = 1; j <= level; j++) System.out.print(j);
            for (int j = level - 1; j >= 1; j--) System.out.print(j);
            System.out.println();
            level++;
        } while (level <= n);

        // for-each遍历展示
        System.out.println("\n===== for-each遍历 =====");
        String[] patterns = {"直角三角形", "等腰三角形", "空心正方形", "九九乘法表", "数字金字塔"};
        int idx = 1;
        for (String p : patterns) {
            System.out.println("  图案" + idx++ + ": " + p);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68

# 5.3.8 循环结构训练题

训练1:使用 while 循环计算 1+2+3+...+100 的结果,然后改写为 for 循环版本,比较两种写法。

训练2:使用 do-while 实现一个简单的菜单系统,包含"查询余额"、"存款"、"取款"、"退出"四个选项,用户选择退出时才结束程序。

训练3:使用嵌套 for 循环打印以下等腰三角形(n=5时):

    *
   ***
  *****
 *******
*********
1
2
3
4
5

训练4:使用 for-each 遍历一个字符串数组,找出最长的字符串并输出。思考:如果需要同时输出最长字符串的索引位置,for-each 还能做到吗?

思考:while(true) 和 for(;;) 有什么区别?在字节码层面是否相同?(提示:可以用 javap 工具反编译查看。)

# 5.4 跳转语句

# 5.4.1 break语句

break 用于跳出当前循环或 switch 语句。

for (int i = 0; i < 10; i++) {
    if (i == 5) {
        break;  // 跳出循环
    }
    System.out.println(i);
}
// 输出:0 1 2 3 4
1
2
3
4
5
6
7

break 在实际开发中的典型应用——查找第一个满足条件的元素:

// 在数组中查找目标值
int[] data = {12, 45, 67, 23, 89, 34};
int target = 23;
int index = -1;
for (int i = 0; i < data.length; i++) {
    if (data[i] == target) {
        index = i;
        break;  // 找到后立即退出,避免无意义的后续遍历
    }
}
System.out.println(target + " 的位置:" + index);  // 3
1
2
3
4
5
6
7
8
9
10
11

# 5.4.2 continue语句

continue 跳过本次循环的剩余代码,直接进入下一次循环的条件判断。

for (int i = 0; i < 10; i++) {
    if (i % 2 == 0) {
        continue;  // 跳过本次循环
    }
    System.out.println(i);
}
// 输出:1 3 5 7 9
1
2
3
4
5
6
7

continue 的典型应用——过滤无效数据:

// 处理数据时跳过无效项
String[] inputs = {"123", "abc", "456", "", "789", null};
int sum = 0;
for (String input : inputs) {
    if (input == null || input.isEmpty()) {
        continue;  // 跳过 null 和空字符串
    }
    try {
        sum += Integer.parseInt(input);
    } catch (NumberFormatException e) {
        continue;  // 跳过无法解析的字符串
    }
}
System.out.println("有效数字之和:" + sum);  // 1368
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 5.4.3 标签和break/continue

Java 支持使用标签配合 break 和 continue 跳出多层循环:

// 使用标签跳出外层循环
outer:
for (int i = 0; i < 5; i++) {
    for (int j = 0; j < 5; j++) {
        if (i == 2 && j == 3) {
            break outer;  // 直接跳出外层循环
        }
        System.out.println("i=" + i + ", j=" + j);
    }
}
1
2
3
4
5
6
7
8
9
10

标签 continue 的用法——跳过外层循环的当前迭代:

// 找出所有不含数字 3 的两位数乘积
outer:
for (int i = 10; i < 20; i++) {
    for (int j = 10; j < 20; j++) {
        int product = i * j;
        String s = String.valueOf(product);
        if (s.contains("3")) {
            continue outer;  // 跳过外层循环的当前迭代
        }
        // 处理不含 3 的乘积
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

对比 C++:C++ 没有标签 break,要跳出多层循环通常需要用 goto(不推荐)或设置标志变量。Java 的标签 break 更加优雅。

注意:Java 没有 goto 语句(虽然 goto 是保留关键字,但没有实现)。

疑惑:标签 break 和 goto 有什么区别?为什么 Java 保留了 goto 关键字但不实现它?

答疑:goto 是无条件跳转,可以跳到代码中的任意位置,包括前跳(跳到已执行过的代码),这导致代码逻辑难以理解和维护(著名的"意大利面条代码")。而 Java 的标签 break/continue 只能跳出包围当前代码的循环/switch 结构,是一种受控的跳转,不能任意跳转。

论证:Java 保留 goto 关键字的目的是防止程序员用它做变量名。如果不保留,万一有人写了 int goto = 10;,将来语言演进时想实现 goto 就会造成兼容性问题。这是语言设计中"预留关键字"的典型策略。

结果展示:所以 Java 的标签跳转本质上是 goto 的安全子集——只能向外跳,不能向前跳,不能跳入其他方法,不能跳到随意位置。这既保留了多层循环跳出的便利性,又避免了 goto 的混乱。

# 5.4.4 跳转语句底层原理

break 和 continue 的字节码实现:在字节码层面,break 和 continue 都是 goto 指令。

  • break:goto 跳转到循环结束后的第一条指令。
  • continue:goto 跳转到循环的条件判断处(对于 for 循环是迭代语句处)。
  • 标签 break:goto 跳转到外层循环结束后的第一条指令。
// for(int i=0; i<10; i++) { if(i==5) break; }
// 字节码简化:
0: iconst_0        // i = 0
1: istore_1
2: iload_1         // 循环条件判断
3: bipush 10
5: if_icmpge 18    // if i >= 10, goto 18(正常退出)
8: iload_1
9: iconst_5
10: if_icmpne 14   // if i != 5, goto 14(不 break)
13: goto 18        // break → 跳到循环后面(和正常退出同一位置)
14: iinc 1, 1      // i++
17: goto 2         // 跳回条件判断
18: ...            // 循环结束
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 5.4.5 综合案例:素数查找器

编写一个程序,综合运用 break、continue 和标签跳转,查找指定范围内的素数。

public class PrimeFinder {
    // 判断素数(用break优化)
    static boolean isPrime(int n) {
        if (n <= 1) return false;
        if (n <= 3) return true;
        if (n % 2 == 0 || n % 3 == 0) return false;
        for (int i = 5; i * i <= n; i += 6) {
            if (n % i == 0 || n % (i + 2) == 0) {
                return false;  // 找到因子,break隐含在return中
            }
        }
        return true;
    }

    public static void main(String[] args) {
        int limit = 100;

        // 使用continue跳过非素数
        System.out.println("===== 1~" + limit + "的素数(continue) =====");
        int count = 0;
        for (int i = 2; i <= limit; i++) {
            if (!isPrime(i)) continue;  // 跳过非素数
            System.out.printf("%4d", i);
            count++;
            if (count % 10 == 0) System.out.println();
        }
        System.out.println("\n共" + count + "个素数");

        // 使用break找到第N个素数
        System.out.println("\n===== 第50个素数(break) =====");
        int target = 50, found = 0, num = 1;
        while (true) {
            num++;
            if (!isPrime(num)) continue;
            found++;
            if (found == target) break;  // 找到目标,跳出
        }
        System.out.println("第" + target + "个素数是: " + num);

        // 使用标签break查找素数对(相邻素数差为2)
        System.out.println("\n===== 前10对孪生素数(标签break) =====");
        int pairCount = 0;
        search:
        for (int i = 3; i < 1000; i += 2) {
            if (!isPrime(i) || !isPrime(i + 2)) continue;
            pairCount++;
            System.out.printf("  (%d, %d)%n", i, i + 2);
            if (pairCount >= 10) break search;  // 标签break
        }

        // 使用标签continue在二维搜索中跳过
        System.out.println("\n===== 素数乘积表(标签continue) =====");
        int[] primes = {2, 3, 5, 7, 11};
        System.out.print("     ");
        for (int p : primes) System.out.printf("%5d", p);
        System.out.println();
        outer:
        for (int p1 : primes) {
            System.out.printf("%3d: ", p1);
            for (int p2 : primes) {
                int product = p1 * p2;
                if (product > 50) {
                    System.out.print("  -- ");
                    continue;  // 跳过但继续打印
                }
                System.out.printf("%5d", product);
            }
            System.out.println();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71

# 5.4.6 跳转语句训练题

训练1:给定一个整数数组,找出第一对相邻的相等元素。例如 {1, 3, 3, 5, 7, 7, 9} 应输出 3(第一对相邻相等的元素)。使用 break 实现。

训练2:给定一个字符串,统计其中元音字母(a、e、i、o、u,不区分大小写)的个数。使用 continue 跳过非元音字母。

训练3:在一个 10×10 的二维数组中查找某个目标值,找到后立即停止搜索。使用标签 break 实现。

思考:在使用标签 break 跳出多层循环时,有人建议用"设置布尔标志变量"的方式替代。两种方式各有什么优缺点?在团队开发中你会选择哪种?

上次更新: 2026/06/10, 11:13:41
字符串和数组
函数方法

← 字符串和数组 函数方法→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式