QUICKREF · LANGUAGE · JEP 361
switch 表达式:让分支返回值
JDK 17 · 速查主题 04 / 06 · 预计 15 分钟
一句话switch 从语句变成表达式后,分支可以直接产出值,箭头标签取消贯穿,编译器还会检查穷尽性。它最适合 enum、有限集合和纯映射逻辑;复杂控制流仍应留在语句里。
学完你要能自己回答
- switch 表达式什么时候必须穷尽?enum 与 String 的规则有什么不同?
- yield 和 return 在 switch 表达式里怎么区分?
- 选择器为 null 时会发生什么?17 能写 case null 吗?
从语句到表达式01
switch 表达式整体产出一个值,可以直接赋给变量、作为 return 的参数。箭头标签 -> 不会贯穿,每个分支只执行自己的代码。
enum Op { ADD, SUB }
static int apply(Op op, int a, int b) {
return switch (op) {
case ADD -> a + b;
case SUB -> {
int r = a - b;
yield r;
}
};
}
System.out.println(apply(Op.ADD, 2, 3));
System.out.println(apply(Op.SUB, 2, 3));
5 -1
- 箭头标签不会 fall through,不需要
break。 - 分支可以是表达式,也可以是代码块;代码块用
yield返回值。 - switch 表达式必须穷尽;enum 覆盖所有常量时可以省略 default。
return在 switch 表达式里表示「从方法返回」,不是「从这个分支产出值」。
箭头标签不是「更短的 case」。它同时改变了控制流语义:没有贯穿、没有隐式落空,分支边界更清晰。
yield:代码块里的返回值02
当分支需要多行逻辑时,用 {} 包住,并在末尾 yield 一个值。yield 只能出现在 switch 表达式的分支块里;switch 语句里不能用它产出值。
static String grade(int score) {
return switch (score / 10) {
case 10, 9 -> "A";
case 8 -> "B";
case 7 -> {
String note = score >= 75 ? "B+" : "C";
yield note;
}
default -> "D";
};
}
| 写法 | JDK 17 行为 |
|---|---|
case X -> value; | 分支直接产出 value |
case X -> { ... yield v; } | 代码块用 yield 产出 v |
case X: ... break; | 传统语句写法,不能作为表达式值 |
return v; 在分支里 | 从整个方法返回,不是产出分支值 |
在 switch 表达式里用
break 带值(break v;)是早期预览语法,14 正式版已经改为 yield。看到旧资料要注意版本。穷尽性:编译器替你查漏03
switch 表达式必须覆盖所有可能值。对 enum,覆盖所有常量即可省略 default;对 String、int 等开放类型,必须提供 default。
enum Level { LOW, HIGH }
static int weight(Level level) {
return switch (level) {
case LOW -> 1;
case HIGH -> 2;
};
}
这段代码没有 default,仍能编译。以后给 Level 增加 MEDIUM,所有这样的 switch 会立即编译失败——这是 enum 穷尽性最有价值的地方。
- enum 的穷尽性按常量名检查;新增常量会破坏所有未写 default 的 switch 表达式。
- String / int 的 switch 表达式没有封闭集合,必须写 default。
- sealed 类型的穷尽性在 17 仍依赖预览的 switch 类型模式;普通 switch 不能按子类型穷尽。
- switch 语句不要求穷尽;switch 表达式要求穷尽,这是两者最重要的区别之一。
如果确实想兜底未来新增的 enum 常量,写 default;但要想清楚:你是想现在编译失败,还是想运行时悄悄走默认分支。
null 选择器04
普通 switch(语句或表达式)在选择器为 null 时抛 NullPointerException。17 的 case null 只在 JEP 406 预览的 switch 类型模式里可用,普通 switch 不能写。
static int code(String s) {
return switch (s) {
case "a" -> 1;
default -> 0;
};
}
try {
System.out.println(code(null));
} catch (NullPointerException e) {
System.out.println("null-npe");
}
null-npe
如果业务上 null 有明确含义,应在 switch 之前显式判断,或使用 17 预览的 case null(并接受预览限制)。不要依赖 default 接住 null:它不会。
21 的 switch 模式匹配正式支持
case null 与 case null, default;17 只有预览版支持,不能作为稳定契约。跨版本迁移时,这段代码必须重新编译并调整。从旧 switch 迁移05
| 旧写法 | 新写法 | 注意 |
|---|---|---|
| 每个 case 都 break | 箭头标签 -> | 不再需要 break,也不会贯穿 |
| 先赋临时变量再 return | switch 表达式直接返回 | 分支必须穷尽 |
| 代码块里给 result 赋值 | yield | yield 只在分支块内 |
| default 抛异常兜底 | enum 覆盖所有常量 | 新增常量时编译失败,更安全 |
| 嵌套 if 判断类型 | 17 预览 switch 类型模式 | 生产代码谨慎,需 --enable-preview |
// JDK 8
int result;
switch (op) {
case ADD:
result = a + b;
break;
case SUB:
result = a - b;
break;
default:
throw new IllegalArgumentException();
}
// JDK 17
int result = switch (op) {
case ADD -> a + b;
case SUB -> a - b;
};
不是所有 switch 都值得改成表达式。分支里有副作用、需要贯穿、或逻辑很复杂时,保留 switch 语句更清楚;表达式的优势在「纯映射」。
版本边界BOUNDARY
这张表比 API 本身更值得记。写代码时先确认你的运行时版本。
| 特性 / API | 状态 | 说明 |
|---|---|---|
| switch 表达式 | 14 正式 | JEP 361;JDK 12 / 13 预览。 |
| yield | 14 正式 | 替代预览期的 break value。 |
| switch 类型模式 | 17 预览 | JEP 406;需 --enable-preview,21 正式。 |
| case null | 17 仅预览 | 普通 switch 选择器为 null 仍抛 NPE。 |
| sealed 穷尽 switch | 17 预览 | 需要类型模式;sealed 本身不改变 switch 规则。 |
| switch 表达式穷尽性 | 14 正式 | enum 可省略 default;String / int 必须 default。 |
费曼自检SELF CHECK
先合上页面,用自己的话回答,再展开对照。能讲出因果和反例,才算真正掌握。
01. switch 表达式和 switch 语句最关键的区别是什么?
表达式必须产出一个值并且必须穷尽;语句不要求穷尽,也不产出值。表达式用箭头标签和 yield,语句仍可使用冒号标签与 break。
02. 什么时候可以省略 default?
只有选择器是 enum 且覆盖了所有常量时可以省略。String、int 等开放类型必须写 default,否则编译失败。
03. yield 和 return 有什么区别?
yield 只从 switch 表达式的分支块产出一个值,控制流继续留在方法内;return 直接从整个方法返回。分支块里误写 return 会改变方法行为。
04. 17 的 switch 遇到 null 会怎样?
普通 switch 抛 NullPointerException,default 不会接住 null。只有 17 预览的 switch 类型模式支持 case null;21 才正式。
学习进度
记录保存在本机浏览器 · 不上传