4.2 switch 语句与表达式
前置知识:条件分支和 Java 17 基础语法
预计时间:30 分钟
完成标志:能区分传统冒号分支、箭头分支和 switch 表达式
传统 switch 语句
String item = "book";
switch (item) {
case "book":
System.out.println("50 credits");
break;
case "magazine":
System.out.println("40 credits");
break;
case "notebook":
System.out.println("35 credits");
break;
default:
System.out.println("unknown item");
}本页采用 Java 17 的正式语法,不启用预览特性。switch 选择器支持 byte、short、char、int 及对应包装类型、枚举和 String。不支持 long、float、double 或 boolean。包装类型、枚举或 String 选择器为 null 时都会抛 NullPointerException;箭头写法和 default 也不会接住它,应在进入前明确处理。
冒号分支会穿透
传统 case: 从匹配位置开始,若未遇到 break、return、throw 或 switch 结束,会继续执行后面的语句:
int day = 6;
switch (day) {
case 6:
System.out.println("Saturday");
case 7:
System.out.println("weekend");
break;
default:
System.out.println("weekday");
}当 day 为 6,会打印两行。这可能是故意共享逻辑,也可能是漏写 break。显式注释有意穿透,并用测试锁定行为。
共享多个标签无需空分支复制代码:
switch (day) {
case 1:
case 2:
case 3:
case 4:
case 5:
System.out.println("weekday");
break;
case 6:
case 7:
System.out.println("weekend");
break;
default:
throw new IllegalArgumentException("day: " + day);
}箭头分支不穿透
Java 14 起正式提供箭头形式,Java 17 可直接使用:
switch (item) {
case "book" -> System.out.println("50 credits");
case "magazine" -> System.out.println("40 credits");
case "notebook" -> System.out.println("35 credits");
default -> System.out.println("unknown item");
}一个分支需要多条语句时使用块:
case "book" -> {
recordSale(item);
System.out.println("50 credits");
}多个常量可写在同一标签:
case "book", "magazine" -> System.out.println("physical item");switch 表达式返回值
int price = switch (item) {
case "book" -> 50;
case "magazine" -> 40;
case "notebook" -> 35;
default -> throw new IllegalArgumentException("item: " + item);
};这是表达式,末尾声明语句需要分号。每条正常路径必须产生兼容结果;用 throw 的路径突然结束,不需要结果值。
块分支通过 yield 提供值:
int price = switch (item) {
case "book" -> {
int base = 50;
System.out.println("featured item");
yield base;
}
case "magazine" -> 40;
default -> 0;
};yield 结束当前 switch 表达式并提供结果,不等同于从整个方法 return。
穷尽性和 default
switch 表达式必须满足编译器的穷尽性检查;这不意味着 null 也被兜底。对开放的整数或字符串集合通常需要 default。对枚举穷尽列出所有常量时,编译器可判断覆盖完整;是否仍写 default 取决于你希望新枚举常量在重新编译时触发编译失败,还是走显式兜底。
不要用 default -> 0 隐藏本应拒绝的未知值。价格、权限、协议状态等场景通常更适合抛出明确异常或返回能表达缺失的类型。
何时选择 switch
- 一个离散值映射到固定分支:适合 switch;
- 范围、多个独立条件或复杂谓词:
if / else if更自然; - 分支不断增长且每个分支包含大量行为:考虑枚举多态、映射表或策略对象,而不是无限扩展 switch。
Python 与 C++ 边界
Python 3.10+ 的 match 是结构化模式匹配,能力和语义不只是 Java switch 的改名,且没有传统穿透。
C++ switch 通常使用整数或枚举选择器,冒号分支也会穿透;它没有 Java 的 String switch,也没有 Java 17 的 switch 表达式箭头与 yield 语义。
专业速查
| 需求 | Java 17 选择 |
|---|---|
| 只执行分支动作 | switch 语句 |
| 计算一个值 | switch 表达式 |
| 避免穿透 | 箭头标签 |
| 多条语句并返回值 | 箭头块 + yield |
| 未知值应失败 | default -> throw ... |
| selector 可能为 null | switch 前显式处理 |
离开这一页前
给 "book"、未知商品和 null 各安排一条路径,再解释冒号分支何时穿透、箭头分支为何不需要 break,以及 yield 只把值交给 switch 表达式而不是退出整个方法。最后确认选择器不是 long 或 boolean。
下一节把一次分支选择扩展为重复处理十项交付记录。