Switch 表达式(JDK 12 预览 / JDK 14 正式)
周三下午的代码评审会上,小崔打开了一个订单状态处理方法——80 行,6 个 case,每个都以 break 结尾。
"这段代码我每次改都要数 break,生怕穿透了。"小崔诉苦道。
孔蓝凑过来看了一眼:"还有两个 case 忘了写 break,线上 bug 就是这么来的。"
白歌敲了敲白板:"JDK 14 的 Switch 表达式彻底解决了这个问题。箭头语法消灭 break 穿透,yield 关键字让 switch 可以返回值——写法和思维都变得清晰了。今天我们就把它从 JDK 14 的发布说明里搬到代码里。"
定义表
| 概念 | 描述 |
|---|---|
| Switch 表达式(Switch Expression) | JDK 14 正式特性,允许 switch 作为表达式返回值,使用箭头语法 -> 和 yield 关键字 |
| 箭头语法(Arrow Case) | case X -> 形式,每个分支独立执行,无穿透(fall-through),无需 break |
| yield 关键字 | 在 Switch 表达式的 case 块中用于返回值,类似于方法的 return,但仅退出当前 switch 块 |
| 穷尽性检查(Exhaustiveness) | 编译器强制 Switch 表达式覆盖所有可能的分支,否则编译报错 |
| 多条 case 标签 | case A, B, C -> 语法,多个值共享同一个分支逻辑,逗号分隔 |
传统 switch 的痛点
在了解 Switch 表达式之前,先回顾传统 switch 语句的三个主要问题:
基础用法:箭头语法消灭 break
场景一:订单状态处理
public class SwitchExpressionBasic {
enum OrderStatus { PENDING, PROCESSING, SHIPPED, DELIVERED, CANCELLED }
public static void main(String[] args) {
OrderStatus status = OrderStatus.SHIPPED;
// === JDK 14 箭头语法:无需 break ===
switch (status) {
case PENDING -> System.out.println("订单待处理");
case PROCESSING -> System.out.println("订单处理中");
case SHIPPED -> System.out.println("订单已发货");
case DELIVERED -> System.out.println("订单已签收");
case CANCELLED -> System.out.println("订单已取消");
}
}
}
输出:
订单已发货
场景二:switch 作为表达式返回值
public class SwitchExpressionValue {
enum EmployeeLevel { JUNIOR, MIDDLE, SENIOR, ARCHITECT }
public static void main(String[] args) {
// === JDK 14:switch 直接返回值,赋值给变量 ===
EmployeeLevel level = EmployeeLevel.ARCHITECT;
int baseSalary = switch (level) {
case JUNIOR -> 8000;
case MIDDLE -> 15000;
case SENIOR -> 25000;
case ARCHITECT -> 35000;
};
System.out.println("等级: " + level + ",基础薪资: " + baseSalary + "元");
// === 飞翔科技员工薪资计算 ===
String[] team = {"大翔", "白歌", "小崔", "孔蓝"};
EmployeeLevel[] levels = {
EmployeeLevel.ARCHITECT,
EmployeeLevel.SENIOR,
EmployeeLevel.JUNIOR,
EmployeeLevel.MIDDLE
};
for (int i = 0; i < team.length; i++) {
int salary = switch (levels[i]) {
case JUNIOR -> 8000;
case MIDDLE -> 15000;
case SENIOR -> 25000;
case ARCHITECT -> 35000;
};
System.out.printf("%s(%s):%d元/月%n", team[i], levels[i], salary);
}
}
}
输出:
等级: ARCHITECT,基础薪资: 35000元
大翔(ARCHITECT):35000元/月
白歌(SENIOR):25000元/月
小崔(JUNIOR):8000元/月
孔蓝(MIDDLE):15000元/月
进阶用法:yield 与代码块
当分支逻辑较为复杂(多行代码)时,使用 yield 关键字在代码块中返回值。
public class SwitchExpressionYield {
enum ProjectPhase { PLANNING, DEVELOPMENT, TESTING, DEPLOYED }
public static void main(String[] args) {
ProjectPhase phase = ProjectPhase.TESTING;
// === yield 在 case 块中返回复杂计算值 ===
int teamSize = switch (phase) {
case PLANNING -> {
System.out.println("需求分析阶段,只需要核心架构师");
yield 2; // 只有大翔和白歌
}
case DEVELOPMENT -> {
System.out.println("开发阶段,全员上阵");
yield 8;
}
case TESTING -> {
System.out.println("测试阶段,需要测试 + 核心开发");
int devCount = 3;
int qaCount = 2;
yield devCount + qaCount; // 可以包含计算逻辑
}
case DEPLOYED -> {
System.out.println("已上线,仅需运维");
yield 1; // 李眉值班
}
};
System.out.println("当前阶段: " + phase + ",所需人数: " + teamSize);
}
}
输出:
测试阶段,需要测试 + 核心开发
当前阶段: TESTING,所需人数: 5
多条 case 标签合并
public class SwitchMultiCase {
enum DayOfWeek { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }
public static void main(String[] args) {
DayOfWeek today = DayOfWeek.WEDNESDAY;
// === 多个 case 共享同一个分支 ===
String schedule = switch (today) {
case MONDAY, TUESDAY, WEDNESDAY, THURSDAY -> "工作日:上午站会 → 编码 → 代码评审";
case FRIDAY -> "周五:上午站会 → 编码 → 下午技术分享会";
case SATURDAY, SUNDAY -> "周末:自由安排(但大翔可能发消息问进度)";
};
System.out.println("今天是" + today + ",日程:" + schedule);
}
}
输出:
今天是WEDNESDAY,日程:工作日:上午站会 → 编码 → 代码评审
穷尽性检查:编译器帮你兜底
Switch 表达式的穷尽性检查强迫覆盖所有枚举值,否则编译报错。这是传统 switch 语句不具备的安全保障。
public class SwitchExhaustiveness {
enum OrderStatus { PENDING, PROCESSING, SHIPPED, DELIVERED, CANCELLED, REFUNDED }
public static void main(String[] args) {
OrderStatus status = OrderStatus.REFUNDED;
// === 穷尽性检查:覆盖所有6个枚举值 ===
String message = switch (status) {
case PENDING -> "待处理";
case PROCESSING -> "处理中";
case SHIPPED -> "已发货";
case DELIVERED -> "已签收";
case CANCELLED -> "已取消";
case REFUNDED -> "已退款";
};
System.out.println("订单状态: " + message);
}
}
输出:
订单状态: 已退款
如果你在
OrderStatus中新增一个枚举值但忘记在 switch 表达式中添加对应 case,编译器会直接报错,不会让代码跑到生产环境。
易错场景
传统 switch 的 break 遗漏陷阱
public class TraditionalSwitchPitfall {
public static void main(String[] args) {
int day = 3;
// === 传统写法:忘记 break 的后果 ===
switch (day) {
case 1:
System.out.println("周一");
// 忘了 break —— 穿透到下一个 case!
case 2:
System.out.println("周二");
case 3:
System.out.println("周三");
case 4:
System.out.println("周四");
case 5:
System.out.println("周五");
}
// 输出从"周三"开始,一直打印到"周五"——这不是你想要的结果!
}
}
输出:
周三
周四
周五
白歌的忠告:如果在传统 switch 中故意使用穿透,务必加上
// fall through注释,否则代码评审时会被打回。
yield 只能在 Switch 表达式中使用
public class YieldScopePitfall {
public static void main(String[] args) {
// ❌ 编译错误:yield 不能用于传统 switch 语句
// int result = 0;
// switch (1) {
// case 1: yield 10; // 错误!传统 switch 语句不能用 yield
// }
// ✅ 正确:switch 表达式 → 用 yield
int result = switch (1) {
case 1 -> {
int temp = 5 + 5;
yield temp; // OK:switch 表达式中使用 yield
}
default -> 0;
};
System.out.println("result = " + result);
}
}
输出:
result = 10
箭头语法不支持穿透
public class ArrowNoFallthrough {
public static void main(String[] args) {
int code = 200;
// === 箭头语法不能穿透,不需要 break ===
switch (code) {
case 200 -> System.out.println("成功");
// case 200 -> 没有穿透到下一个分支的概念
case 404 -> System.out.println("未找到");
case 500 -> System.out.println("服务器错误");
}
}
}
输出:
成功
面试考点
面试官常问的三个问题:
问题一:"JDK 14 的 Switch 表达式和传统 switch 语句有什么区别?"
答案:三个核心区别。第一,语法上箭头
->替代了冒号:,每个分支独立执行不穿透,消灭了 break 遗漏 bug。第二,switch 可以作为表达式返回值,通过-> 值或yield返回。第三,编译器强制穷尽性检查——Switch 表达式必须覆盖所有可能的枚举值或包含 default 分支,否则编译报错。这是传统 switch 不具备的安全保障。
问题二:"yield 和 return 的区别是什么?"
答案:
return退出整个方法,yield仅退出当前 switch 表达式并返回值。在 switch 表达式的代码块中不能使用return来给 switch 返回值——用yield。此外,yield只能在 Switch 表达式的 case 块中使用,传统 switch 语句(非表达式)中不能使用 yield。
问题三:"多条 case 标签合并 case A, B -> 和传统写法 case A: case B: 有什么不同?"
答案:传统多 case 标签使用穿透(两个 case 叠在一起),代码意图不够直观。箭头语法的
case A, B, C ->用逗号分隔共享分支,语义更明确——"这些值都走同一个逻辑"。编译器会检查每个 case 只出现一次,避免重复标签的隐患。
小崔的代码评审笔记:"原来 80 行的 switch 改成箭头语法后只用了 20 行。最爽的是删掉了所有 break——'删代码'永远是程序员最快乐的事。白歌说穷尽性检查是编译器帮我们写单元测试,我深表赞同。"