流程语句
# 05.流程语句
# 目录介绍
# 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 + "个偶数"); // 顺序
}
}
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 训练题
选择题:以下关于程序流程结构的说法,正确的是:
- A. Java 只支持顺序结构和循环结构
- B. 所有流程控制在 CPU 层面最终转化为跳转指令
- C. 选择结构只能通过 if-else 实现
- D. 循环结构至少执行一次循环体
填空题:从 CPU 角度看,顺序结构对应 ___(程序计数器)自然递增;选择结构对应 ___ 跳转指令;循环结构对应 ___ 跳转和 ___ 跳转的组合。
思考题:有人说"任何程序逻辑都可以只用顺序、选择、循环三种结构来表达"(结构化程序定理)。请思考:递归算法是否可以用这三种结构改写?如果可以,改写后有什么优缺点?
# 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("及格");
}
2
3
4
# 5.2.3 if-else语句
int score = 55;
if (score >= 60) {
System.out.println("及格");
} else {
System.out.println("不及格");
}
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("不及格");
}
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岁,不能开车");
}
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
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;
}
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");
}
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");
}
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); // 星期三
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;
};
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;
};
}
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 值的分布选择不同的字节码指令:
- tableswitch:当 case 值连续或接近连续时使用。内部维护一个跳转表(数组),通过索引直接跳转,时间复杂度 O(1)。例如
case 1, 2, 3, 4, 5会使用 tableswitch。 - 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
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();
}
}
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++;
}
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);
}
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);
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();
}
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);
}
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;
}
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 → 退出循环
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);
}
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);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
for-each 的限制:
- 无法获取索引:如果需要索引,必须使用传统 for 循环。
- 无法修改集合:在 for-each 中调用
list.add()或list.remove()会触发ConcurrentModificationException。 - 无法反向遍历:只能正向遍历。
- 无法遍历多个集合:不能同时遍历两个集合。
对比 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();
}
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();
}
// 输出:
// * * * * *
// * *
// * *
// * *
// * * * * *
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;
}
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: ... // 循环结束
2
3
4
5
6
7
8
9
10
11
12
13
14
关键点:第7行 if_icmpge 是条件跳转(不满足条件时退出),第17行 goto 是无条件跳转(跳回循环头部)。这就是循环的本质。
JIT 编译器对循环的优化:
- 循环展开(Loop Unrolling):JIT 可能将小循环展开为连续语句,减少跳转次数。
- 循环不变量外提(Loop-Invariant Code Motion):将循环中不变的计算移到循环外面。
- 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++) {
// ...
}
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);
}
}
}
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时):
*
***
*****
*******
*********
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
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
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
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
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);
}
}
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 的乘积
}
}
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: ... // 循环结束
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();
}
}
}
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 跳出多层循环时,有人建议用"设置布尔标志变量"的方式替代。两种方式各有什么优缺点?在团队开发中你会选择哪种?