Skip to content

控制流程:程序的决策大脑

难度:初级 | 预计时间:35 分钟 | 前置:变量与类型


📖 嵌套地狱

故事背景

你的产品经理扔过来一份促销规则: · 普通用户满 200 减 20 · VIP 用户满 100 打 9 折 · 超级 VIP 直接 8.5 折,且与其他优惠叠加 · 优惠券(满减券/折扣券)可叠加 · 有些商品不参与活动

你写了 8 层嵌套的 if-else,三个月后 bug 来了,你自己都看不懂。控制流程本身简单,但写得好与坏,代码可读性天差地别

💻 代码演示

对比两种实现:左边的嵌套地狱 vs 右边的守卫子句 + switch 表达式(Java 17):

java
// 重构前:嵌套地狱
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-elseswitch)、循环forwhiledo-while)、分支跳转breakcontinuereturn)。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++)、增强 for for (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>