JDK17 Switch表达式完全体:从语句到表达式的语法革命
2026/4/30大约 10 分钟
JDK17 Switch表达式完全体:从语句到表达式的语法革命
适读人群:对Java switch改进有兴趣、想写更简洁分支代码的开发者 | 阅读时长:约15分钟
开篇故事
2020年,我做代码审查时看到一段这样的代码,我数了一下,34行:
String dayName;
switch (day) {
case MONDAY:
dayName = "周一";
break;
case TUESDAY:
dayName = "周二";
break;
case WEDNESDAY:
dayName = "周三";
break;
// ... 还有四种情况
default:
dayName = "未知";
}这段代码最让我烦的有三点:
- 每个case都要写
break,忘了就会fall-through(贯穿)导致Bug dayName必须提前声明,无法直接赋值- 34行代码,有效信息7行,其余27行全是样板
同事小李用了新写法,三行搞定:
String dayName = switch (day) {
case MONDAY -> "周一";
case TUESDAY -> "周二";
// ...
default -> "未知";
};Switch从语句(Statement)变成了表达式(Expression),这是Java语法历史上的重大改进。今天把整个演进史和完整用法讲透。
一、Switch的演进历史
1.1 版本时间线
JDK1.0:switch语句,仅支持int/char/byte/short
JDK5:支持枚举
JDK7:支持String
JDK14:Switch表达式正式GA(JEP 361)
- 箭头标签 ->(无fall-through)
- yield关键字
- switch可作为表达式使用
JDK17:配合Sealed Classes使用更完美
JDK21:Switch Pattern Matching正式GA(JEP 441)
- case可以匹配类型
- 支持guard条件(when)
- null处理1.2 旧switch的四大痛点
痛点1:Fall-through(贯穿)
case "a":
case "b":
// 这个还好
case "c":
doSomething();
// 忘了break -> 继续执行case "d"的逻辑!
case "d":
doOther();
break;
痛点2:不能作为表达式
// 无法这样写:
String result = switch(x) { ... };
痛点3:必须提前声明变量
String s;
switch(x) { case 1: s = "one"; break; ... }
痛点4:代码冗长
break; break; break; break;(每个case都要)二、Switch表达式深度解析
2.1 两种新语法对比
// 新语法1:箭头标签(-> )不会fall-through,直接返回值
int result = switch (value) {
case 1 -> 100;
case 2 -> 200;
case 3, 4 -> 300; // 多标签合并
default -> 0;
};
// 新语法2:传统冒号标签 + yield关键字(需要多行代码块时)
int result2 = switch (value) {
case 1: yield 100; // yield代替return,从switch返回值
case 2: yield 200;
case 3, 4: { // 代码块
int temp = value * 100;
yield temp; // 必须有yield
}
default: yield 0;
};
// 注意:新旧语法可以混用,但不推荐(可读性差)2.2 箭头标签的代码块形式
// 箭头后面也可以是代码块(需要yield)
String label = switch (code) {
case "US" -> "美国";
case "CN" -> "中国";
case "JP" -> {
// 需要做复杂处理时,用代码块
String name = "日本";
System.out.println("Processing JP: " + name);
yield name; // 代码块必须用yield返回值
}
default -> "其他国家";
};2.3 Switch作为语句vs表达式的区别
Switch语句(Statement):
- 不返回值
- 可以不处理所有情况(可以没有default)
- 不强制穷举
Switch表达式(Expression):
- 必须返回值(除了void上下文)
- 必须穷举所有情况(编译器检查)
- 结合sealed类,无需default
区分方法:
- 赋值给变量:switch(x) {...}; → 表达式
- 传给方法参数:foo(switch(x){...});→ 表达式
- 单独语句:switch(x){...};→ 语句2.4 JDK21 Switch Pattern Matching的升级
JDK21新增:
1. case可以是类型模式:case String s -> ...
2. guard条件(when):case Integer i when i > 0 -> ...
3. null处理:case null -> ...
4. 配合sealed类,编译器穷举检查Mermaid图(Switch演进):
三、完整代码示例
3.1 从旧写法到新写法的全面对比
import java.time.*;
import java.util.*;
/**
* Switch表达式完整示例
* 引入版本:JDK12~13(Preview);GA版本:JDK14(2020年3月,JEP 361)
* Pattern Matching for switch GA:JDK21(JEP 441)
*/
public class SwitchExpressionDemo {
enum Day { MON, TUE, WED, THU, FRI, SAT, SUN }
enum Season { SPRING, SUMMER, AUTUMN, WINTER }
// ===== 场景1:基本枚举映射 =====
// 旧写法(switch语句)
static String getDayNameOld(Day day) {
String name;
switch (day) {
case MON: name = "周一"; break;
case TUE: name = "周二"; break;
case WED: name = "周三"; break;
case THU: name = "周四"; break;
case FRI: name = "周五"; break;
case SAT: name = "周六"; break;
case SUN: name = "周日"; break;
default: throw new IllegalArgumentException("Unknown day: " + day);
}
return name;
}
// 新写法(switch表达式)
static String getDayNameNew(Day day) {
return switch (day) {
case MON -> "周一";
case TUE -> "周二";
case WED -> "周三";
case THU -> "周四";
case FRI -> "周五";
case SAT -> "周六";
case SUN -> "周日";
// 不需要default!枚举的所有情况都已覆盖
};
}
// ===== 场景2:多标签合并(消除重复逻辑)=====
// 旧写法(fall-through合并)
static boolean isWeekendOld(Day day) {
switch (day) {
case SAT:
case SUN:
return true;
default:
return false;
}
}
// 新写法(多标签合并)
static boolean isWeekendNew(Day day) {
return switch (day) {
case SAT, SUN -> true; // 多标签,清晰明了
case MON, TUE, WED, THU, FRI -> false;
};
}
// ===== 场景3:需要复杂逻辑的case(代码块+yield)=====
static String getSeasonDescription(Season season) {
return switch (season) {
case SPRING -> "万物复苏";
case SUMMER -> {
// 复杂逻辑
var temp = LocalDate.now().getMonthValue();
String suffix = (temp >= 7) ? "酷暑" : "初夏";
yield "骄阳似火," + suffix; // yield返回值
}
case AUTUMN -> "金桂飘香";
case WINTER -> {
yield "天寒地冻";
}
};
}
// ===== 场景4:switch表达式在更复杂上下文中使用 =====
static void printSchedule(Day day) {
// switch表达式直接在方法调用中使用
System.out.printf("%-3s: %s%n",
getDayNameNew(day),
switch (day) {
case MON -> "9:00 站会";
case WED -> "14:00 周会";
case FRI -> "16:00 代码审查";
default -> "自由开发时间";
}
);
}
// ===== 场景5:JDK21 Pattern Matching for switch =====
sealed interface Notification
permits Notification.Email, Notification.SMS, Notification.Push {}
record Notification.Email(String to, String subject, String body) implements Notification {}
record Notification.SMS(String phone, String message) implements Notification {}
record Notification.Push(String deviceToken, String title, String content) implements Notification {}
static String formatNotification(Notification notification) {
return switch (notification) {
case Notification.Email(var to, var subject, var body) ->
// JDK21: Record Pattern解构
String.format("Email to %s: [%s] %s", to, subject, body);
case Notification.SMS sms when sms.message().length() > 70 ->
// JDK21: when guard条件
String.format("Long SMS to %s: %s...", sms.phone(),
sms.message().substring(0, 67));
case Notification.SMS sms ->
String.format("SMS to %s: %s", sms.phone(), sms.message());
case Notification.Push push ->
String.format("Push [%s]: %s", push.title(), push.content());
};
}
// ===== 场景6:处理null(JDK21)=====
static String processValue(Object value) {
return switch (value) {
case null -> "null值"; // JDK21:显式处理null
case Integer i when i < 0 -> "负整数: " + i; // 带guard条件
case Integer i when i == 0 -> "零";
case Integer i -> "正整数: " + i;
case String s when s.isEmpty() -> "空字符串";
case String s -> "字符串: " + s;
case List<?> list -> "列表,大小: " + list.size();
default -> "其他类型: " + value.getClass().getSimpleName();
};
}
public static void main(String[] args) {
System.out.println("=== 星期名称 ===");
for (var day : Day.values()) {
System.out.println(getDayNameNew(day) + ": weekend=" + isWeekendNew(day));
}
System.out.println("\n=== 季节描述 ===");
for (var season : Season.values()) {
System.out.println(season + ": " + getSeasonDescription(season));
}
System.out.println("\n=== 周计划 ===");
for (var day : Day.values()) {
printSchedule(day);
}
System.out.println("\n=== 通知格式化 ===");
var notifications = List.of(
new Notification.Email("alice@ex.com", "Hello", "How are you?"),
new Notification.SMS("13800138000", "Your code is 123456"),
new Notification.SMS("13900139000", "A".repeat(80)), // 超长短信
new Notification.Push("device123", "新消息", "你有一条未读消息")
);
notifications.forEach(n -> System.out.println(formatNotification(n)));
System.out.println("\n=== 类型处理(JDK21)===");
for (var v : new Object[]{null, -5, 0, 42, "", "hello", List.of(1, 2)}) {
System.out.println(processValue(v));
}
}
}3.2 状态机实现(Switch表达式的经典应用)
import java.util.*;
/**
* 用Switch表达式实现有限状态机
*/
public class StateMachineDemo {
enum State { IDLE, PROCESSING, PAUSED, COMPLETED, FAILED }
enum Event { START, PAUSE, RESUME, COMPLETE, FAIL, RESET }
record StateTransition(State from, Event event, State to, String action) {}
// ===== 旧写法:嵌套if/switch =====
static State transitionOld(State current, Event event) {
switch (current) {
case IDLE:
if (event == Event.START) return State.PROCESSING;
break;
case PROCESSING:
switch (event) {
case PAUSE: return State.PAUSED;
case COMPLETE: return State.COMPLETED;
case FAIL: return State.FAILED;
default: break;
}
break;
case PAUSED:
if (event == Event.RESUME) return State.PROCESSING;
if (event == Event.FAIL) return State.FAILED;
break;
case COMPLETED:
case FAILED:
if (event == Event.RESET) return State.IDLE;
break;
default: break;
}
throw new IllegalStateException("Invalid transition: " + current + " + " + event);
}
// ===== 新写法:Switch表达式 =====
static State transition(State current, Event event) {
return switch (current) {
case IDLE -> switch (event) {
case START -> State.PROCESSING;
default -> throw new IllegalStateException(current + "+" + event);
};
case PROCESSING -> switch (event) {
case PAUSE -> State.PAUSED;
case COMPLETE -> State.COMPLETED;
case FAIL -> State.FAILED;
default -> throw new IllegalStateException(current + "+" + event);
};
case PAUSED -> switch (event) {
case RESUME -> State.PROCESSING;
case FAIL -> State.FAILED;
default -> throw new IllegalStateException(current + "+" + event);
};
case COMPLETED, FAILED -> switch (event) {
case RESET -> State.IDLE;
default -> throw new IllegalStateException(current + "+" + event);
};
};
}
// 状态机执行
static State runStateMachine(State initial, List<Event> events) {
State current = initial;
for (var event : events) {
System.out.printf(" %s + %s -> ", current, event);
try {
current = transition(current, event);
System.out.println(current);
} catch (IllegalStateException e) {
System.out.println("ERROR: " + e.getMessage());
}
}
return current;
}
public static void main(String[] args) {
System.out.println("=== 正常流程 ===");
runStateMachine(State.IDLE, List.of(
Event.START, Event.PAUSE, Event.RESUME, Event.COMPLETE
));
System.out.println("\n=== 失败流程 ===");
runStateMachine(State.IDLE, List.of(
Event.START, Event.FAIL, Event.RESET
));
System.out.println("\n=== 非法转换 ===");
runStateMachine(State.IDLE, List.of(
Event.START, Event.START // PROCESSING -> START 非法
));
}
}3.3 Switch在数据转换中的应用
import java.util.*;
import java.util.stream.*;
/**
* Switch表达式在数据转换/映射场景的完整应用
*/
public class SwitchDataTransform {
// HTTP状态码映射
static String httpStatusDescription(int code) {
return switch (code / 100) { // 用除法分组
case 1 -> "1xx 信息响应";
case 2 -> switch (code) { // 嵌套switch
case 200 -> "OK";
case 201 -> "Created";
case 204 -> "No Content";
default -> "2xx 成功";
};
case 3 -> switch (code) {
case 301 -> "Moved Permanently";
case 302 -> "Found";
case 304 -> "Not Modified";
default -> "3xx 重定向";
};
case 4 -> switch (code) {
case 400 -> "Bad Request";
case 401 -> "Unauthorized";
case 403 -> "Forbidden";
case 404 -> "Not Found";
case 429 -> "Too Many Requests";
default -> "4xx 客户端错误";
};
case 5 -> switch (code) {
case 500 -> "Internal Server Error";
case 502 -> "Bad Gateway";
case 503 -> "Service Unavailable";
default -> "5xx 服务器错误";
};
default -> "Unknown Status Code";
};
}
// 货币符号
enum Currency { CNY, USD, EUR, GBP, JPY }
static String currencySymbol(Currency currency) {
return switch (currency) {
case CNY -> "¥";
case USD -> "$";
case EUR -> "€";
case GBP -> "£";
case JPY -> "¥";
};
}
// 复杂的类型转换(JDK21 Pattern Matching)
record Money(double amount, Currency currency) {}
static String formatMoney(Object value) {
return switch (value) {
case null -> "N/A";
case Money m -> String.format("%s%.2f", currencySymbol(m.currency()), m.amount());
case Integer i -> String.format("¥%d.00", i);
case Double d -> String.format("¥%.2f", d);
case String s -> s; // 已经是字符串格式
default -> value.toString();
};
}
public static void main(String[] args) {
System.out.println("=== HTTP状态码 ===");
for (int code : new int[]{200, 201, 301, 400, 404, 500, 503, 999}) {
System.out.printf("%d: %s%n", code, httpStatusDescription(code));
}
System.out.println("\n=== 货币符号 ===");
for (var currency : Currency.values()) {
System.out.printf("%s: %s%n", currency, currencySymbol(currency));
}
System.out.println("\n=== 金额格式化 ===");
for (var value : new Object[]{null, new Money(99.5, Currency.CNY),
42, 3.14, "¥100.00"}) {
System.out.println(formatMoney(value));
}
}
}四、踩坑实录
坑1:旧语法和新语法的混用
// 错误:在同一个switch里混用箭头和冒号标签
int x = 1;
String result = switch (x) {
case 1 -> "one";
case 2: // 编译错误!不能混用
yield "two";
default -> "other";
};
// 正确:只用箭头(推荐)
String r1 = switch (x) {
case 1 -> "one";
case 2 -> "two";
default -> "other";
};
// 或者只用冒号+yield
String r2 = switch (x) {
case 1: yield "one";
case 2: yield "two";
default: yield "other";
};坑2:Switch表达式必须穷举所有情况
// 编译错误:switch表达式未覆盖所有int值
// int x = 5;
// String s = switch (x) { // 编译错误!int有无穷多值,必须有default
// case 1 -> "one";
// case 2 -> "two";
// };
// 正确:必须有default
int x = 5;
String s = switch (x) {
case 1 -> "one";
case 2 -> "two";
default -> "other"; // 必须!
};
// 枚举例外:枚举是有限集合,如果覆盖了所有枚举值,可以不用default
enum Color { RED, GREEN, BLUE }
Color color = Color.RED;
String name = switch (color) {
case RED -> "红色";
case GREEN -> "绿色";
case BLUE -> "蓝色";
// 不需要default(所有枚举值都覆盖了)
};坑3:yield vs return vs break
// 注意:yield是switch表达式专用的,不能用于switch语句
// return是退出方法,break是退出switch语句(不是yield)
class YieldVsReturn {
int computeOld(int x) {
switch (x) {
case 1: return 100; // return退出整个方法
case 2: break; // break退出switch
default: break;
}
return -1;
}
int computeNew(int x) {
return switch (x) {
case 1 -> 100; // 直接返回
case 2 -> {
System.out.println("case 2");
yield 200; // yield返回switch表达式的值
// return 200; // 编译错误!switch表达式里不能用return
}
default -> -1;
};
}
}坑4:Switch的null处理(JDK21之前)
// JDK21之前:switch不能处理null,会抛NullPointerException
String s = null;
try {
switch (s) { // JDK21之前:NPE!
case "hello" -> System.out.println("hello");
default -> System.out.println("other");
}
} catch (NullPointerException e) {
System.out.println("NPE in switch");
}
// 解决方案1(JDK21之前):在switch之前检查null
if (s != null) {
switch (s) { ... }
} else {
// 处理null
}
// 解决方案2(JDK21+):在switch里处理null
switch (s) {
case null -> System.out.println("null");
case "hello" -> System.out.println("hello");
default -> System.out.println("other");
}坑5:Guard条件(when)的顺序很重要
// JDK21 when条件
Object obj = 42;
String result = switch (obj) {
// 错误顺序(更具体的case应该在前面)
// case Integer i -> "integer"; // 这个会匹配所有Integer,后面的case不可达!
// case Integer i when i > 0 -> "positive"; // 不可达!
// 正确顺序:更具体的条件在前
case Integer i when i < 0 -> "negative: " + i;
case Integer i when i == 0 -> "zero";
case Integer i -> "positive: " + i; // 没有guard,匹配所有剩余Integer
default -> "not integer";
};
System.out.println(result);五、总结与延伸
5.1 Switch表达式特性一览表
| 特性 | 最低JDK版本 | 说明 |
|---|---|---|
-> 箭头标签 | JDK14 GA | 无fall-through,直接返回值 |
yield 关键字 | JDK14 GA | 代码块中返回switch表达式的值 |
多标签合并(case A, B ->) | JDK14 GA | 消除重复case |
| Switch作为表达式 | JDK14 GA | 可以赋值、传参 |
类型Pattern(case String s) | JDK21 GA | Pattern Matching for switch |
Guard条件(when) | JDK21 GA | case的附加过滤条件 |
null处理(case null) | JDK21 GA | switch可处理null |
5.2 版本兼容建议
- JDK14(GA):箭头标签和switch表达式基本功能,可以放心使用
- JDK17:配合Sealed Classes使用穷举检查,效果更好
- JDK21(LTS):Pattern Matching for switch GA,可以全面使用
- 升级建议:从旧switch语句迁移到switch表达式,能大幅减少代码量
