Java 模式匹配如何简化 instanceof 和 switch?
简化版
instanceof 模式匹配在 Java 16 正式发布,把类型检查和安全转换合并,并根据控制流判断模式变量作用域;switch 模式匹配在 Java 21 正式发布,可直接按类型、守卫条件和 null 分支匹配。编写 switch 时还必须满足分支不被前面更宽的模式支配,并对可能输入保持穷尽。
详细版
if (obj instanceof String s) 只有匹配成功时才引入 s,null 不会匹配类型模式。编译器做流敏感作用域分析,因此在 && 右侧可以使用变量,在可能未匹配的 || 右侧则不能使用。
模式 switch 按从上到下的首个适用分支执行,具体类型应写在宽泛类型之前;Java 21 使用 when 添加守卫。传统 switch 遇到 null 通常抛 NullPointerException,模式 switch 可以显式声明 case null,但不会因为支持模式就默认接纳 null。
完整版教学
一、instanceof 模式变量
传统写法要先检查再强制转换,类型名重复且容易转错:
if (value instanceof String) {
String text = (String) value;
System.out.println(text.length());
}
模式匹配将测试、转换和变量声明合为一体:
if (value instanceof String text && !text.isBlank()) {
System.out.println(text.length());
}
只有左侧模式成功,&& 右侧才执行,所以 text 在那里肯定已赋值。写成 value instanceof String text || text.isBlank() 则不能编译,因为进入右侧时可能正是模式失败的情况。
二、流敏感作用域
模式变量不是简单地从声明位置一直存活到代码块末尾,编译器会根据控制流判断它是否必然匹配:
if (!(value instanceof String text)) {
return;
}
System.out.println(text.length());
执行到打印语句意味着失败分支已经返回,因此 text 必然存在。理解这点比死记某一种 if 写法更可靠。类型模式不会匹配 null,所以上述成功路径中的 text 也不会是 null。
三、switch 类型模式与守卫
static String format(Object value) {
return switch (value) {
case null -> "空值";
case Integer number when number > 0 -> "正整数";
case Integer number -> "其他整数:" + number;
case String text -> "文本:" + text;
default -> "其他类型";
};
}
守卫使用 when,只在类型模式匹配后判断附加条件。带守卫的正整数分支必须放在普通 Integer 分支之前,否则前面的宽泛分支会先接住所有 Integer。
四、支配关系与穷尽性
模式 switch 从上到下选择首个适用标签,所以 case Object object 若放在 case String text 前,会让 String 分支永远不可达,编译器将这种情况判为支配错误。这与传统 switch 中重复 case 的错误目的相同:阻止无意义分支。
switch 表达式必须覆盖所有可能值。选择器是普通开放类型时,通常需要 default;选择器是 sealed 类型时,列出所有允许分支后可以不写 default,编译器能基于密封层次检查穷尽性。显式 case null 与类型穷尽性是两个维度,因为类型模式本身不匹配 null。
五、Record Pattern 的配合
Java 21 还正式加入 Record Pattern,可以直接解构 Record 组件,并与类型模式嵌套:
record Point(int x, int y) {}
static int distanceSquared(Object value) {
return switch (value) {
case Point(int x, int y) -> x * x + y * y;
default -> 0;
};
}
这适合读取透明数据载体,但不应把一个复杂 switch 堆满业务逻辑。模式匹配改善的是类型检查和数据提取,领域多态更自然时,仍可把行为放回各类型的方法中。
六、版本边界
instanceof 模式匹配由 JEP 394 在 Java 16 定稿;switch 模式匹配由 JEP 441、Record Pattern 由 JEP 440 在 Java 21 定稿。旧版本曾使用预览语法,尤其守卫语法经历过变化,面试和项目代码应先确认目标 JDK,避免照搬早期预览示例。
七、用分支顺序推演支配关系
假设选择器声明类型为 Object,运行值是字符串。case CharSequence cs 已经覆盖所有 String,因此若后面再写 case String s,后者永远不可达;把 String 放前、CharSequence 放后才有意义。守卫不是任意常量 case 的通用 if,Java 21 的 when 只跟在模式标签之后。
| 前置 case | 后置 case | 是否可达 | 原因 |
|---|---|---|---|
String s | Object o | 是 | Object 还能接其他类型 |
Object o | String s | 否 | String 已被 Object 支配 |
Integer i when i > 0 | Integer i | 是 | 后者接剩余整数 |
Integer i | Integer i when i > 0 | 否 | 所有 Integer 已被接住 |
一个 sealed 层次有 3 个允许分支时,switch 列出 3 个具体类型即可通过穷尽检查;新增第 4 个允许分支后,旧源码重新编译会被提示补分支。运行时若类型层次与编译时不一致,所谓穷尽也可能以 MatchException 失败,因此部署时仍要保证类版本一致。
记忆钩子:模式 switch 按“从窄到宽”排列;类型覆盖负责穷尽,
case null单独负责空值,二者不要混为一谈。
八、常见误区与追问
- 误区:
instanceof String s在 value 为 null 时也会得到 s=null。 类型模式对 null 不匹配,成功路径中的模式变量为非 null。 - 误区:模式变量的作用域只由大括号决定。 编译器做流敏感分析,提前 return 后变量也可能在后续路径可用。
- 误区:模式 switch 会自动接受 null。 未显式
case null时仍可能抛 NullPointerException,类型模式不会接住 null。 - 误区:宽类型 case 放前面只是风格不好。 它会支配后续窄类型,编译器将不可达分支判错。
- 追问:switch pattern statement 也必须穷尽吗? Java 21 中使用模式或 null 标签等增强形式的 switch statement 也受穷尽性检查。
- 追问:Record Pattern 何时正式发布? JEP 440 与 switch 模式 JEP 441 都在 Java 21 定稿,不应混用旧预览守卫语法。
- 追问:sealed 层次为什么有助于穷尽检查? 编译器知道许可的直接子类型集合,可以验证所有可达分支是否覆盖。
九、加强记忆
instanceof 模式把“判断、转换、声明”合成一步,变量是否可用由控制流决定。switch 模式按顺序选择首个匹配,具体分支放前、宽泛分支放后,守卫用 when;同时记住类型模式不吃 null,分支还要满足支配与穷尽规则。