Java——模式匹配演进史:从 JEP 406 到 JEP 441
Java 的模式匹配是十几轮 JEP 叠加出来的:instanceof 模式三轮(JDK 14 预览、15 再预览、16 定稿),switch 上的模式匹配五轮(JDK 17~20 四次预览、21 定稿),记录模式三轮(19、20 预览、21 定稿),未命名变量两轮(21 预览、22 定稿),而原始类型模式到 JDK 27 已经是第五次预览,仍然没有定稿。
这篇文章按 JEP 的顺序回答另一类问题:每一轮究竟改了什么、为什么不得不改,以及那些只在预览期存在过的语法去了哪里。
文中所有编译器输出都来自本机实测(JDK 17.0.5 / JDK 21.0.8 / JDK 25+37-LTS,Apple M1 Pro,macOS);凡是标注「实测」的结论,都能在下面找到对应的原始输出块。JEP 的编号、版本、状态与引用的动机,全部核对自 openjdk.org 的 JEP 页面正文。
先看一条时间线
把后面要展开的东西压成一张表,方便随时对照:
| JEP | JDK | 状态 | 关键变化 |
|---|---|---|---|
| 305 | 14 | 预览 | instanceof 模式首次预览 |
| 375 | 15 | 第二次预览 | 与 JDK 14 的预览完全一致,只为继续收集反馈 |
| 394 | 16 | 定稿 | 模式变量不再隐式 final;恒真的 instanceof 模式改为编译错误 |
| 406 | 17 | 预览 | switch 支持模式;case null;守卫模式 p && b;括号模式 |
| 420 | 18 | 第二次预览 | 常量标签须排在同类型守卫模式之前;sealed 泛型层次的穷尽性更精确;total pattern 也匹配 null |
| 427 | 19 | 第三次预览 | 守卫模式 && 被 when 子句替换;null 语义回归传统 switch |
| 405 | 19 | 预览 | 记录模式首次预览(含具名记录模式) |
| 432 | 20 | 第二次预览 | 泛型记录模式类型参数推导;记录模式进入增强 for 头部;移除具名记录模式 |
| 433 | 20 | 第四次预览 | enum 穷尽 switch 改抛 MatchException;switch 标签文法简化;switch 中支持泛型记录模式推导 |
| 440 | 21 | 定稿 | 移除增强 for 头部的记录模式 |
| 441 | 21 | 定稿 | 移除括号模式;允许限定枚举常量;when 守卫、支配与穷尽性规则定稿 |
| 443 | 21 | 预览 | 未命名模式与未命名变量 _ |
| 456 | 22 | 定稿 | 未命名变量与模式,无改动定稿 |
| 455 | 23 | 预览 | 原始类型进入模式、instanceof 与 switch |
| 488 | 24 | 第二次预览 | 无改动 |
| 507 | 25 | 第三次预览 | 无改动 |
| 530 | 26 | 第四次预览 | 增强无条件精确性(unconditional exactness)定义;更严格的支配检查 |
| 532 | 27 | 第五次预览 | 无改动;截至 JDK 27 GA 仍未定稿 |
表内所有 JEP 的页头 Status 字段都是 Closed / Delivered,Release 就是表里的 JDK 版本号。JDK 27 的发布日期是 2026-09-15,JEP 532 的 History 段落写的是「re-previewed by JEP 530 (JDK 26) … We here propose to preview it for a fifth time, without change」。
从 instanceof 说起:三轮换来的一个语法
样板代码的问题在哪
JDK 16 之前,判断类型再取用要写三段:
1 | class BeforePatterns { |
JEP 375 的 Motivation 段落把这件事拆得很细:这段代码里同时发生了三件事——测试、转换、声明新局部变量;重复出现三次的类型名既淹没了逻辑本身,又「provides opportunities for errors to creep unnoticed into programs」。
三轮的节奏是这样的:
- JEP 305 在 JDK 14 首次预览;
- JEP 375 在 JDK 15 原样再预览,它的 History 写得很直接:「This JEP proposes to re-preview the feature in JDK 15, with no changes relative to the preview in JDK 14, in order to gather additional feedback」;
- JEP 394 在 JDK 16 定稿,并且带着两处修改。
JEP 394 的 History 列出的两处修改是:
- 「Lift the restriction that pattern variables are implicitly final, to reduce asymmetries between local variables and pattern variables.」(模式变量不再是隐式
final,减少它与普通局部变量的不对称); - 「Make it a compile-time error for a pattern instanceof expression to compare an expression of type S against a pattern of type T, where S is a subtype of T.」(恒真的 instanceof 模式是编译错误,因为这种判断「will always succeed and is then pointless」)。
一个可运行的例子
1 | import java.util.List; |
1 | non-blank string: hello |
这段代码还展示了两条容易被忽略的规则:s 的作用域由流程分析决定,o instanceof String s && !s.isBlank() 里 s 只在右侧表达式为真时可见;同一个方法里可以出现两处 o instanceof String s,因为它们处在互斥的分支上,前一处不满足时那个变量并不在作用域内。
那条「恒真即错误」的规则,在 JDK 21 被放宽了
固定类型对父类型做 instanceof 判断,JDK 16 起是错误,但这条限制没有一直保留。
1 | public class InstanceofUnconditional { |
同一份源码,只换 --release:
1 | $ javac --release 17 InstanceofUnconditional.java |
实测的释放版本矩阵(JDK 25 的 javac,--release 17/18/19/20 全部报「不支持 instanceof 中的无条件模式」,--release 21/22/23/24/25 全部通过)说明这条限制是在 JDK 21 被放开的。编译器提示里的英文原文是 unconditional patterns in instanceof are not supported in -source 17,而「无条件模式(unconditional pattern)」正是 JEP 441 用来描述 case Object o、case String s 这类模式的术语——JEP 441 的正文写道「the type pattern String s unconditionally matches a selector expression of type String」。JDK 21 用一套更精确的支配/穷尽性规则,替换掉了 JDK 16 那刀切式的禁令。(把这两件事关联起来是我的解读,JEP 页面本身没有为这条放宽单独立项,能确证的只有编译器行为。)
switch 上的模式匹配:四轮预览
第一轮 JEP 406(JDK 17):把 switch 变成分派器
JEP 406 的 Goals 列了六条,其中三条决定了后面几轮的走向:
- 「Introduce two new kinds of patterns: guarded patterns, to allow pattern matching logic to be refined with arbitrary boolean expressions, and parenthesized patterns, to resolve some parsing ambiguities.」:守卫模式与括号模式都是这一轮引入的;
- 「Allow the historical null-hostility of
switchto be relaxed when desired.」:switch遇到null必抛异常的传统要放宽; - 「Ensure that all existing
switchexpressions and statements continue to compile with no changes and execute with identical semantics.」:既有 switch 代码必须一字不改、语义不变。
最后一条是往后所有争论的底线。它解释了为什么模式匹配不能”顺手”改掉 switch 的旧行为,只能加法式地扩展。
1 | public class ShapeSwitch { |
1 | area=12.00 |
area 没有 default 分支:Shape 是 sealed 的,编译器知道只有三种直接子类型,能证明这个 switch 表达式覆盖了全部取值。而 legacy 那条 if/else 链遇到 Rect 会悄悄地返回 unknown——注意这不是抛异常,是静默地走错分支,正是 JEP 406 描述的「overly general control construct」带来的病灶。
在 JDK 17 上跑这份源码需要预览开关,实测:
1 | $ javac ShapeSwitch.java |
JDK 25 上同样的源码直接编译运行,输出一致——这是这个特性少有的”从预览到定稿没有改过表现”的部分。
第二轮 JEP 420(JDK 18):排序规则与 sealed 泛型的穷尽性
第二次预览只改了两处:
- 「Dominance checking now forces a constant case label to appear before a guarded pattern of the same type, for readability」:常量标签必须排在同类型的守卫模式之前,理由是可读性;
- 「Exhaustiveness checking of switch blocks is now more precise with sealed hierarchies where the permitted direct subclass only extends an instantiation of the (generic) sealed superclass」:sealed 泛型层次结构下的穷尽性判断更精确了。
第二处是纯粹的实现难度:当 sealed 父类型是泛型、而 permitted 子类只继承了某个具体实例化时,穷尽性推导要跟着泛型走。
这一轮还有一条只活了一个版本的规则。JEP 420 正文写的是:
If the selector expression evaluates to null then any null case label or a total pattern case label is said to match.
在 JDK 18 的预览里,case Object o 这种对选择器类型「全覆盖」的模式是能接住 null 的,同一份 JEP 还写了 case p(p 是 total pattern)会支配 case null。到 JDK 19 这个说法被整段删掉了。
第三轮 JEP 427(JDK 19):when 取代 &&,null 回到传统语义
JEP 427 的 History 只有两条,但每条都推翻了自己上一轮:
- 「Guarded patterns are replaced with
whenclauses in switch blocks.」:守卫模式p && b被when子句取代; - 「The runtime semantics of a pattern switch when the value of the selector expression is
nullare more closely aligned with legacyswitchsemantics.」:null的运行时语义向传统 switch 靠拢。
JEP 427 的 Alternatives 段落给出了放弃 && 的理由:
An alternative to guarded pattern labels is to support guarded patterns directly as a special pattern form, e.g.
p && e. Having experimented with this in previous previews, the resulting ambiguity with boolean expressions have lead us to preferwhenclauses in pattern switches.
「与布尔表达式产生歧义」是纯语法层面的问题:case String t && t.length() > 3 里,&& 到底属于模式语法还是布尔表达式语法,读者和解析器都要犹豫。换成 when 之后,守卫的边界一眼可见。
1 | public class GuardDemo { |
1 | when as identifier = 3 |
那段代码里之所以能写 int when = 3;,是因为 when 是上下文关键字而不是保留字,存量代码里叫 when 的变量不受影响。这种”把新关键字藏进上下文”的做法,正是为了满足 JEP 406 那条「既有代码一字不改」的目标。
而 && 与括号模式的消失,是可以直接拿编译器验证的:
1 | public class GuardedPatternAnd { |
1 | public class ParenthesizedPattern { |
这两份源码在 JDK 17 的预览下能编译能跑:
1 | $ javac --enable-preview --release 17 ParenthesizedPattern.java GuardedPatternAnd.java |
到 JDK 25 上,它们连解析都过不去:
1 | $ javac ParenthesizedPattern.java |
注意报错的样子:删除语法留下的是一串语法解析错误,没有「这个语法已被移除,请改用 when」这样的提示。这是预览功能的一项隐性成本——用预览语法写的代码,在语法被删掉之后连错误信息都不会友好。
null 那条规则的回退同样可以直接看到。JDK 18 预览里 total pattern 能接住 null,JDK 19 之后不能:
1 | public class TotalPatternNull { |
1 | matched object: x |
case Object x 对 Object 选择器是无条件模式,但它接不住 null(JDK 21 与 JDK 25 实测结果相同)。JEP 441 的最终表述是「With a case null, the switch executes the code associated with that label; without a case null, the switch throws NullPointerException, just as before」,并且补了一句「To maintain backward compatibility with the current semantics of switch, the default label does not match a null selector」——default 也不吃 null。
第四轮 JEP 433(JDK 20):MatchException 与文法简化
JEP 433 的 History 列了三处改动:
- 「An exhaustive switch … over an enum class now throws
MatchExceptionrather thanIncompatibleClassChangeErrorif no switch label applies at run time.」 - 「The grammar for switch labels is simpler.」
- 「Inference of type arguments for generic record patterns is now supported in switch expressions and statements, along with the other constructs that support patterns.」
第一条是不兼容改动——JEP 433 自己写的是「This is a minor incompatible change to the language」,理由是「To align with pattern switch semantics」。为什么 enum switch 会有一个运行期才会触发的兜底分支?因为枚举随时可能新增常量:已经编译好的穷尽 switch 在遇到新常量时会落到编译器合成的兜底分支上。JEP 433 对它的描述是「An exhaustive switch over an enum fails to match only if the enum class is changed after the switch has been compiled, which is highly …」。
这件事可以在本机完整复现:先按两个常量的枚举编译调用方,再把枚举扩成三个常量并只重编译枚举,最后运行那份旧的调用方 class。
1 | // EnumSwitchMain.java:依赖同目录的 Color.java,此处编译时 Color 只有 RED、GREEN 两个常量 |
把 Color 换成 public enum Color { RED, GREEN, BLUE } 重新编译后,运行同一个 EnumSwitchMain.class,只换编译时的 --release:
1 | --- --release 20 --- |
JDK 20 语言级别编译出来的兜底分支抛 IncompatibleClassChangeError,JDK 21 语言级别抛 MatchException。这就是 JEP 433 那句话在运行时的样子(异常里没有消息文本,只有类型和栈帧,因为它是编译器合成的兜底)。
第二条「文法简化」是那种不会出现在 release note 里、但读者能感觉到的变化:case null, String s -> 这类混合标签的写法被重新规整过。第三条泛型推导的收益在下面的记录模式部分一起看。
记录模式:两个预览、一个撤回的语法
记录模式的版本线和 switch 模式匹配是交织的,JEP 441 里专门提到两者「has co-evolved … with which it has considerable interaction」。
- JEP 405(JDK 19)首次预览,当时记录模式还可以具名,写成
Point(var x, var y) p; - JEP 432(JDK 20)第二次预览,History 里三条改动:加泛型推导、允许出现在增强 for 头部、移除具名记录模式;
- JEP 440(JDK 21)定稿。
1 | public class RecordPattern { |
1 | distance=7 |
case Line(Point(var x1, var y1), Point(var x2, var y2)) 一次拆到叶子;case Box(Box(var inner)) 里两个 Box 的类型参数都不需要写,靠 JEP 432 引入的推导——这是 JDK 19 预览时做不到的。
被移除的具名记录模式,在 JDK 25 上是一条硬邦邦的语法错误:
1 | public class NamedRecordPattern { |
1 | $ javac NamedRecordPattern.java |
更短命的是「记录模式放在增强 for 头部」这个语法:JEP 432 在 JDK 20 加入它,JEP 440 在定稿时把它删掉,History 的说法是「the main change since the second preview is to remove support for record patterns appearing in the header of an enhanced for statement. This feature may be re-proposed in a future JEP.」——只活了一个版本。
1 | import java.util.List; |
1 | $ javac RecordPatternInFor.java # JDK 21 |
JEP 432 里这个语法是有正式语义的(等价于引入一个临时变量再对每个元素做模式匹配),但它在定稿前被判定为「不必现在要」。这类撤回对语言设计是好事,对使用者是坏事:如果有人在 JDK 20 上写了它,升级到 21 就要改代码。
定稿:JEP 441 与 JEP 440(JDK 21)
删掉括号模式
JEP 441 的 History 只有两条实质改动,第一条是「Remove parenthesized patterns, since they did not have sufficient value」。
把三轮连起来看:JEP 406 引入括号模式的理由是「to resolve some parsing ambiguities」,它服务的是 p && b 那套守卫语法;JEP 427 把守卫改成 when 之后,括号模式失去存在理由,JEP 441 以「价值不足」把它删掉。前面那两个对照实验(JDK 17 能编译、JDK 25 报「非法的类型开始」)就是这条链条的两端。
允许限定枚举常量
第二条改动是「Allow qualified enum constants as case constants in switch expressions and statements」。JEP 441 对背景的说明是:
It has long been a requirement that, when switching over an enum type, the only valid case constants were enum constants. But this is a strong requirement that becomes burdensome with the new, richer forms of switch.
To maintain compatibility with existing Java code, when switching over an enum type a case constant can still use the simple name of a constant of the enum type being switched over.
旧写法(枚举选择器 + 常量简名)必须继续可用;新增的是「限定名可以出现在任何选择器类型上,只要赋值兼容」。JEP 441 给的例子可以直接跑:
1 | public class QualifiedEnumConstant { |
1 | Heads / Tails |
选择器是接口类型 Currency 时,限定名 Coin.HEADS 合法;而简名只在选择器就是这个枚举类型时才合法:
1 | public class UnqualifiedEnumConstant { |
1 | $ javac UnqualifiedEnumConstant.java |
switch 一旦能处理任意类型,枚举常量就不再天然属于某一种选择器类型,标签合法性、穷尽性判断、导入规则都要跟着重算。
支配与穷尽性:编译器替人守规矩
JEP 441 把「支配(dominance)」这条规则讲得最清楚:
It is a compile-time error for a case label in a switch block to be dominated by any preceding case label in that switch block. This dominance requirement ensures that if a switch block contains only type pattern case labels, they will appear in subtype order.
以及一条排序建议:
All of this suggests a simple, predictable, and readable ordering of case labels in which the constant case labels should appear before the guarded pattern case labels, and those should appear before the unguarded pattern case labels.
实测三条编译器检查,先看模式之间的支配:
1 | public class Dominance { |
1 | $ javac Dominance.java |
常量标签排在无守卫模式之后,同样被拒绝(JDK 21 与 JDK 25 报错文本一致):
1 | public class DominatedConstant { |
1 | $ javac DominatedConstant.java |
穷尽性则是支配的镜像——支配保证”每个标签都有用”,穷尽保证”没有取值漏网”:
1 | public class NotExhaustive { |
1 | $ javac NotExhaustive.java |
守卫让支配分析变得不可判定,JEP 441 对此的处理是:如果守卫是常量 true 才认为它能支配别的标签,此外一概不分析,理由是「a problem which is undecidable in general」。规范里还把守卫模式支配常量标签写成了规则(「A guarded pattern case label dominates a constant case label if the same pattern case label without the guard does」),不过按这份文本去试,实测 JDK 21 与 JDK 25 都接受「守卫模式在前、常量在后」的写法,只有无守卫模式在前时才会报支配错误。规范文本与实现之间的这点出入,写代码时不必指望编译器拦住。
null 的最终规则
null 的处理在四个版本里来回改,最终定下来的是 JDK 21 这套:
- 有
case null就匹配,没有就抛NullPointerException(JEP 441:「A switch block without acase nulllabel is treated as if it has acase nullrule whose body throwsNullPointerException」); default不匹配null;- 可以把
null和default合成一个标签(JEP 441:「It is meaningful, and not uncommon, to want to combine anullcase with adefault. To that end we allownullcase labels to have an optionaldefault」)。
1 | public class NullDemo { |
1 | explicit(null) = matched null |
1 | public class NullCombined { |
1 | string x |
case null, default 在 JDK 21 与 JDK 25 上输出一致。
未命名变量:一次预览就定稿的那次
JEP 443 在 JDK 21 预览了 _(未命名模式与未命名变量),JEP 456 在 JDK 22 定稿,History 只有一句:「We here propose to finalize this feature without change.」——在一串反复修订的 JEP 里,这是唯一一次”零改动定稿”。
JEP 456 规定的可用位置是一张清单:局部变量声明、try-with-resources 的资源说明、基本 for 循环头、增强 for 循环头、catch 的异常参数、lambda 的形式参数;未命名模式则用在类型模式里。它还引出了更早的一段历史:「JEP 302 (Lambda Leftovers) examined the issue of unused lambda parameters and identified the role of underscore to denote them, but also covered many other issues which were handled better in other ways.」——用下划线表示”不用这个参数”的想法至少可以上溯到 JDK 11 的 Lambda Leftovers。
1 | import java.util.List; |
1 | skip: x |
catch (NumberFormatException _)、Point _、for (int i = 0, _ = tick(); ...) 分别覆盖了异常参数、未命名模式和基本 for 循环头三种位置。
版本差异在这个特性上特别明显,同一份源码:
1 | import java.util.List; |
1 | $ javac UnderscoreNeedsPreview.java # JDK 21,不加预览开关 |
_ 的语义是”声明了但不能用”,这一点也和普通变量不同:
1 | public class UnderscoreName { |
1 | $ javac UnderscoreName.java |
JDK 21 上这条报错更啰嗦一些,还会说明 _ 只能用于声明:as of release 21, the underscore keyword '_' is only allowed to declare unnamed patterns, local variables, exception parameters or lambda parameters。措辞本身说明 _ 在 Java 里早就不是自由字符(Java 9 起它就是关键字),所以才能被选来承担这个语义——如果换一个普通标识符,存量代码里已经用了它的地方全都要改。
原始类型模式:五轮预览,仍未定稿
到 JDK 27 为止,这条支线已经预览了五次:
- JEP 455(JDK 23)首次预览,允许原始类型出现在模式、instanceof 与 switch 里;
- JEP 488(JDK 24)第二次预览,History:「re-previewed … without change」;
- JEP 507(JDK 25)第三次预览,同样「without change」;
- JEP 530(JDK 26)第四次预览,带了改动:「We here propose to preview it for a fourth time with two changes: Enhance the definition of unconditional exactness, and apply tighter dominance checks in switch constructs. These changes enable the compiler to identify a wider range of coding errors, although a small number of switch constructs that were previously legal will now be rejected.」
- JEP 532(JDK 27)第五次预览,「without change」。
JEP 530 的两条改动都发生在编译器的判定逻辑里:unconditional exactness(转换是否无需运行期检查就能保证无损)的定义被加强,支配检查跟着变严——带来的后果是「少量以前合法的 switch 写法会被拒绝」。这属于语言设计里最难缠的一类改动:把以前能编译的老代码判为错误。
JEP 455 的 Motivation 把要缓解的痛点列得很具体:switch 只接受 byte/short/char/int,boolean、float、double、long 都不行;instanceof 只接受引用类型;手工写的范围检查(if (i >= -128 && i <= 127) 然后再强转)持续了近三十年;而 int 到 float 这种可能丢精度的转换却能在赋值里悄悄发生。
1 | public class PrimitivePreview { |
上面这份源码在 JDK 25 上(第三次预览)实测:
1 | $ javac PrimitiveNeedsPreview.java # 同样的写法,只是不加 --enable-preview |
第一行输出是 switch 上的数值分派,200 does not fit in a byte 是 instanceof byte b 做的无损转换判定(200 装不进 byte,模式不匹配),最后一行是布尔 switch。
而在 JDK 21 上,即使打开预览开关也过不去——那时这个特性还不存在:
1 | $ javac --enable-preview --release 21 PrimitivePreview.java |
required: class or array 这句话本身就是 JDK 21 的语言模型:case 标签后面必须是类或数组类型。原始类型模式要动的正是这条最底层的规则,这也解释了为什么它需要五次预览:它要重写”模式适用性、无条件性、匹配”这三个谓语在原始类型上的定义。
设计启示:反复来自哪里
把上面这些改动归类,反复基本上来自四个地方,而不是”设计能力不够”。
**一、存量代码不能坏。**JEP 406 把「Ensure that all existing switch expressions and statements continue to compile with no changes and execute with identical semantics」写进了 Goals;JEP 441 保留 default 不匹配 null 的旧行为;新关键字选用上下文关键字 when(实测 int when = 3; 依然合法);枚举常量简名的旧写法在 JEP 441 里被明确保留;_ 之所以能被选中,是因为它在 Java 9 起就已经是关键字,没有存量代码在用。每一次”看起来优雅”的重命名,都要先过一遍兼容性。
二、语法歧义要现场消除。p && b 被 when 取代,官方给的唯一理由是它与布尔表达式产生歧义;括号模式是为消除这类歧义引入的,守卫语法一换它就失去价值,于是在 JEP 441 被删;case null, String s ->、case null, default 这些特殊标签形式,都是在”标签语法要保持可读”和”要和旧语法共存”之间磨出来的。JEP 433 那句轻描淡写的「The grammar for switch labels is simpler」,背后是一次文法重排。
**三、有些判定在理论上就很难。**守卫让支配分析变成不可判定问题,JEP 441 直接接受这个限制(只看守卫是否为常量 true);sealed 泛型层次的穷尽性判断在 JEP 420 被修正过一次;MatchException 不是随手挑的异常,它要配合 JEP 433 引入的运行期兜底语义;原始类型模式更是拖了五轮——JEP 530 还要回头加强 unconditional exactness 的定义,代价是「少量以前合法的写法会被拒绝」。
**四、预览机制本身的成本。**JEP 12 把预览特性定义为「fully specified, fully implemented, and yet impermanent」,并提醒「the feature may change subtly in Java SE $N+1」。本文里这样的例子有五个:括号模式、p && b 守卫、具名记录模式、增强 for 头部的记录模式、以及”total pattern 匹配 null”这条运行时规则。它们都在某个预览版本里正式存在过,然后被删掉或改掉,而且删除之后留下的通常只是解析错误。预览特性不要用在需要长期维护的公共 API 上,用在一次性脚本或内部试验里才划算。
总结
- instanceof 模式用了三轮:JDK 14 预览、15 原样再预览、16 定稿。定稿时取消模式变量隐式
final,并把”恒真的 instanceof 模式”改成编译错误,但这条限制在 JDK 21 又被放宽(实测--release 17报错、--release 21通过)。 - switch 模式匹配用了四轮预览加一次定稿:JEP 406(17)引入模式、
case null、守卫模式p && b与括号模式;JEP 420(18)要求常量标签排在守卫模式之前,并细化 sealed 泛型的穷尽性;JEP 427(19)用when取代&&,并把null语义退回传统 switch;JEP 433(20)让枚举穷尽 switch 改抛MatchException(实测--release 20抛IncompatibleClassChangeError、--release 21抛MatchException)并简化标签文法;JEP 441(21)定稿。 - 记录模式用了两轮预览加一次定稿:JEP 405(19)引入具名记录模式,JEP 432(20)加泛型推导、把记录模式放进增强 for 头部、删掉具名记录模式,JEP 440(21)定稿时又把增强 for 头部那个语法删掉:它只活了一个版本。
- 未命名变量是唯一”一次预览零改动定稿”的:JEP 443(21)→ JEP 456(22);
_可以声明但不能使用(实测读取_直接报错)。 - 原始类型模式到 JDK 27 已经五轮预览(455 → 488 → 507 → 530 → 532),仍未定稿;JEP 530 还收紧了支配检查,代价是少量旧写法被拒绝。
- 定稿后的核心规则:
case null可选、没有则抛 NPE、default不匹配null、case null, default可合并;标签不能被前面的标签支配,sealed/枚举的 switch 表达式必须穷尽(实测三种报错文本)。 - 反复的四个来源是兼容存量代码、消除语法歧义、判定逻辑本身的难度,以及预览机制的成本;预览特性虽然能提前用,但语法被删后的报错往往只是解析错误,不适合放进长期维护的公共代码。
参考资料
- JEP 305: Pattern Matching for instanceof (Preview)
- JEP 375: Pattern Matching for instanceof (Second Preview)
- JEP 394: Pattern Matching for instanceof
- JEP 406: Pattern Matching for switch (Preview)
- JEP 420: Pattern Matching for switch (Second Preview)
- JEP 427: Pattern Matching for switch (Third Preview)
- JEP 433: Pattern Matching for switch (Fourth Preview)
- JEP 441: Pattern Matching for switch
- JEP 405: Record Patterns (Preview)
- JEP 432: Record Patterns (Second Preview)
- JEP 440: Record Patterns
- JEP 443: Unnamed Patterns and Variables (Preview)
- JEP 456: Unnamed Variables & Patterns
- JEP 455: Primitive Types in Patterns, instanceof, and switch (Preview)
- JEP 488: Primitive Types in Patterns, instanceof, and switch (Second Preview)
- JEP 507: Primitive Types in Patterns, instanceof, and switch (Third Preview)
- JEP 530: Primitive Types in Patterns, instanceof, and switch (Fourth Preview)
- JEP 532: Primitive Types in Patterns, instanceof, and switch (Fifth Preview)
- JEP 12: Preview Features
- JEP 361: Switch Expressions
系列索引:Java 系列,语言特性与运行时的长文集







