控制流程:程序的决策大脑
难度:初级 | 预计时间:35 分钟 | 前置:变量与类型
📖 嵌套地狱
故事背景
你的产品经理扔过来一份促销规则: · 普通用户满 200 减 20 · VIP 用户满 100 打 9 折 · 超级 VIP 直接 8.5 折,且与其他优惠叠加 · 优惠券(满减券/折扣券)可叠加 · 有些商品不参与活动
你写了 8 层嵌套的 if-else,三个月后 bug 来了,你自己都看不懂。控制流程本身简单,但写得好与坏,代码可读性天差地别。
💻 代码演示
对比两种实现:左边的嵌套地狱 vs 右边的守卫子句 + switch 表达式(Java 17):
// 重构前:嵌套地狱
public double calcPrice(User user, Order order) {
if (user != null) {
if (order != null) {
if (order.getItems().size() > 0) {
double total = 0;
for (Item item : order.getItems()) {
if (item.isParticipating()) {
total += item.getPrice() * item.getQuantity();
}
}
if (user.isVip()) {
if (user.isSuperVip()) {
total *= 0.85;
} else if (total >= 100) {
total *= 0.9;
}
} else if (total >= 200) {
total -= 20;
}
return total;
}
}
}
return 0;
}
// 重构后:守卫子句 + Switch 表达式 (Java 17)
public double calcPrice(User user, Order order) {
if (user == null || order == null || order.getItems().isEmpty()) {
return 0;
}
double total = order.getItems().stream()
.filter(Item::isParticipating)
.mapToDouble(item -> item.getPrice() * item.getQuantity())
.sum();
return applyDiscount(user, total);
}
private double applyDiscount(User user, double total) {
return switch (user.getLevel()) {
case SUPER_VIP -> total * 0.85;
case VIP -> total >= 100 ? total * 0.9 : total;
default -> total >= 200 ? total - 20 : total;
};
}运行输出:
两种实现运算结果相同,但右边的可读性和可维护性远胜左边。🎯 三个核心问题
这是什么?
Java 控制流程分为条件判断(if-else、switch)、循环(for、while、do-while)、分支跳转(break、continue、return)。Java 17 新增了 Switch 表达式——可以用箭头语法和 yield 返回值。
为什么需要它?
嵌套过深(>3层)是人类短期记忆的杀手。你阅读代码时需要在脑子里维护一个"上下文栈",每多一层嵌套就多压一层——很快就溢出了。守卫子句(Guard Clause)把异常情况提前 return,让主逻辑保持在左侧一条线。这跟前端写的 early return 模式完全一样。
如果没有它会怎样?
如果只有 goto 没有结构化的控制流(这是 C 语言之前的时代),代码会充满跳跃,逻辑像意大利面条。结构化控制流让程序的执行路径可以静态分析,IDE 才能做重构、死代码检测等。
📝 原理讲解
Java 控制流速查:
if-else if-else:和 JS 一模一样,但条件必须是
boolean(没有 truthy/falsy)。Switch 表达式(Java 17):可以用箭头
->,一行代码不需要 break。可以返回值赋给变量。for 循环:传统
for (int i=0; i<n; i++)、增强 forfor (Item item : items)(类似 JS 的 for-of)。while / do-while:和 JS 一致。
前端对比:Java 没有
===vs==的问题(基本类型==比值,引用类型==比地址,用.equals()比内容)。没有三元之外的 truthy/falsy——条件只能写 boolean 表达式。
🎨 生活类比
类比理解
程序流程就像地铁换乘——if-else 是分岔路口(到了某站决定往左还是往右),for/while 是环线(不停地在圈上跑直到到达目的地),switch 是多出口枢纽(根据你的方向直接送到对应站台)。好的设计让你一眼看到换乘路线,坏的设计让你在地下通道里绕晕。
守卫子句(Guard Clause)就像在换乘通道口立个牌子:"此路不通,请走 B2 出口"——先把不可能的情况排除掉,剩下的就是金光大道。
✏️ 动手练习
练习 1
用 switch 表达式实现一个方法,输入月份(1-12),返回该月的天数(不考虑闰年)。
<details> <summary>💡 查看提示</summary>
可以用箭头语法每组写多个值:case 1, 3, 5, 7, 8, 10, 12 -> 31;
</details>
练习 2
写一个 FizzBuzz:遍历 1-100,3 的倍数输出 Fizz,5 的倍数输出 Buzz,同时是 3 和 5 的倍数输出 FizzBuzz。
<details> <summary>💡 查看提示</summary>
同时是 3 和 5 的倍数 = 是 15 的倍数。把判断 15 放在最前面(否则会被 3 或 5 的条件先捕获)。
</details>
练习 3
重构一段嵌套验证逻辑:用户注册时需要验证用户名不为空、密码长度 ≥ 8、邮箱格式正确、用户名未被占用。用守卫子句改写。
<details> <summary>💡 查看提示</summary>
每一层验证不通过就立即 return 对应错误信息,最下面是注册成功的逻辑。
</details>
✅ 自检站
<details> <summary><strong>Java 的 switch 表达式(Java 17)和传统 switch 语句有什么区别?</strong></summary>
传统 switch 贯穿(fall-through),需要手动 break,不能返回值。Switch 表达式用箭头语法(->),不需要 break,可以用 yield 返回值,整个 switch 块可以作为赋值表达式的右值。编译器还会检查是否覆盖了所有可能的分支(对 enum 尤其有用)。
</details>
<details> <summary><strong>为什么嵌套 if-else 超过 3 层就很难维护?有什么替代方案?</strong></summary>
人类短期记忆只能同时处理大约 3-4 层嵌套上下文。替代方案:①守卫子句(early return)提前排除异常情况;②提取方法,把内层逻辑封装成独立方法;③策略模式或 Map 分发(对于复杂的多分支逻辑)。
</details>