JDK 26 基本类型模式怎样影响模式匹配
来源:17golang原创
时间:2026-10-21 11:35:50 492浏览 收藏
如果一段 Java 代码要同时判断 Integer、Long 和 null,旧写法往往要先判断引用类型,再手动拆箱,最后补上范围和空值处理。JDK 26 的 JEP 530(第四次预览)把基本类型带入更多模式匹配场景:可以在 instanceof 中测试基本类型,也可以在 switch 和 record pattern 中使用基本类型。它减少的是分支样板,不是所有转换风险。
官方地址:https://docs.oracle.com/en/java/javase/26/language/primitive-types-patterns-instanceof-switch.html
迁移时先把输入的静态类型、运行时包装类型和可能的 null 分开看。primitive pattern 只有在测试转换安全时才匹配;预览语法也不会把 null 变成一个可以自动拆箱的数值。
先看基线:旧代码为什么需要多层判断
假设路由层收到的是 Object,业务要把整数型配置分成“普通整数”和“较大整数”。在 JDK 25 及以前,instanceof 的右侧只能写引用类型,代码需要先识别包装类,再显式取值:
static String classify(Object input) {
if (input instanceof Integer integer) {
// 先匹配包装类型,再读取已经完成拆箱的 int 值。
return integer >= 0 ? "non-negative-int" : "negative-int";
}
if (input instanceof Long value) {
// Long 与 Integer 是两种不同的包装类型,不能合并成一个 primitive 分支。
return value > Integer.MAX_VALUE ? "large-long" : "small-long";
}
// null 和其它对象都落到这里,业务还要决定是否分别处理。
return "other";
}
这段基线的分支数量不是性能基准,而是迁移时可观察的维护基线:两个包装类型判断、一个兜底分支,以及每个分支中不同的转换语义。若再加入 Short、Float 或记录组件,重复会继续增加。
基本类型 pattern 改变了哪一层语法
JDK 26 预览语法允许在模式位置写基本类型。下面的例子展示“值能否安全转换成 int”这一判断;它不是把任意对象强行转换成 int。为了尝试预览特性,编译和运行都需要使用 JDK 26 与 --enable-preview:
static String asInt(Object input) {
if (input instanceof int value) {
// 只有测试转换精确且可安全完成时,value 才会进入分支。
return "int=" + value;
}
if (input instanceof long value) {
// long 也可以作为目标模式,但超出 int 的值不会凭空变成 int。
return "long=" + value;
}
return "not-an-integer-pattern";
}
// 编译:javac --release 26 --enable-preview PatternDemo.java
// 运行:java --enable-preview PatternDemo
这里最容易误读的是“范围更大”不等于“转换一定安全”。一个 int 值可以转换为某些 long 值而不丢失信息,但转换到 float 时还要考虑有效精度;测试转换会阻止不精确的匹配。JDK 26 的语言说明把这类规则归入安全转换,而不是简单套用普通强制转换。

switch 中要分清 selector、case 常量和 pattern
switch 的变化更适合用“输入类型—匹配形式—结果”来理解。JDK 26 允许更多基本类型作为 selector,并允许用基本类型 pattern 描述值的类型,再用 when 缩小范围:
static String bucket(Object input) {
return switch (input) {
case int value when value {
// when 只限制已经匹配到的 int 值,不负责把 Object 变成 int。
yield "negative-int";
}
case int value -> {
// 同一类型的无守卫 pattern 要放在更具体的 guarded pattern 之后。
yield "non-negative-int";
}
case long value -> "long=" + value;
case null -> "null";
default -> "other";
};
}
上面的顺序表达了一个编译期边界:带 when 的分支先处理,未加守卫的 case int value 不能放在它前面,否则前面的无守卫 pattern 会把后者覆盖。default 也不等于“自动接住 null”;要把 null 作为输入状态处理,就明确写 case null。
如果 selector 本身就是 long、float、double 或 boolean,case 常量必须遵守对应的常量类型规则;不能因为 int 可以拓宽到 double,就把任意整数文字混在 double selector 的 case 中。case 常量的可赋值关系,与 case pattern 的测试转换关系不是同一套规则。
装箱边界:Object、包装类和 null 不是一回事
JEP 530 的价值之一,是允许程序对“值被放进 Object 后又想按数值类型判断”的场景写得更直接;但运行时对象依然可能是 Integer、Long 或其它包装实例。模式匹配不会把一个 Long 里超出范围的值安全地变成 int。
static String inspect(Object input) {
return switch (input) {
case int value -> "matched-int=" + value;
case long value -> "matched-long=" + value;
case Integer boxed -> {
// 这个分支与前面的 int pattern 讨论的是不同视角,实际代码应避免重叠设计。
yield "boxed-integer=" + boxed;
}
case null -> "missing";
default -> "unsupported";
};
}
实际迁移时不要机械地把所有包装类分支和 primitive pattern 同时保留。先决定 API 想表达的是“运行时对象属于哪个包装类”,还是“值能否安全地按某个 primitive 解释”。前者更接近类型分类,后者更接近数值转换测试;二者混排会让后续维护者误判分支是否互斥。

record pattern 里,基本类型组件也要看转换方向
如果 record 的组件是数值类型,primitive pattern 可以出现在解构位置。重点仍然是组件的静态类型和目标 pattern 是否允许安全测试转换,而不是“record 一拆开就可以随意缩窄”。
record Reading(long value, String unit) {}
static String format(Object input) {
return switch (input) {
case Reading(int value, String unit) when value >= 0 -> {
// 这里要求 long 组件到 int pattern 的匹配边界是安全的。
yield value + " " + unit;
}
case Reading(long value, String unit) -> {
// 兜底分支保留原始 long 语义,避免把大值截断。
yield value + " " + unit;
}
default -> "not-a-reading";
};
}
这个例子同时说明了一个设计选择:需要小范围计算时,可以用更窄的 primitive pattern;需要保持原始数据时,则保留 long。若业务允许截断,应在普通代码中显式说明并测试,不要把 pattern 的“匹配成功”误解成强制转换的许可。
用三组指标判断迁移是否值得
本特性主要减少的是模式匹配的样板和转换分支,因此迁移评估应该记录可维护性指标,而不是只看字节码或一次微基准。可以选一段真实分支做前后对照:
| 观察项 | 旧写法基线 | 迁移后重点 |
|---|---|---|
| 分支数量 | 包装类判断、显式取值、null 兜底分别出现 | pattern 与 when 是否让每个业务状态只出现一次 |
| 转换点 | 手写拆箱、强转或范围判断较分散 | 安全测试转换是否集中在 pattern 边界 |
| 回归样本 | 常规值覆盖较多,极值与 null 容易遗漏 | 补齐包装类型、越界值、负值、浮点精度和 null |
如果代码只是把几个 if 换成更短的 switch,但没有减少歧义,迁移收益可能不大。反过来,对接收异构配置、事件或记录的边界层,primitive pattern 能把“识别类型”和“安全取值”放在同一个结构里,通常更容易审阅。
上线前先守住这几个边界
- 把 JEP 530 当作预览特性,编译和运行环境都明确使用 JDK 26 与
--enable-preview,不要把它当成已经永久定型的语法。 - 分别准备
Integer、Long、极小值、极大值、负值、浮点近似值和 null 的输入样本。 - 检查 guarded pattern 与无守卫 pattern 的顺序,避免 dominance 规则让代码无法编译或让意图不清楚。
- 在 API 边界明确“按包装类分类”还是“按安全数值转换匹配”,不要同时写出互相覆盖的分支。
- 需要保留原始数值时使用宽类型 pattern;需要缩窄时把精度、范围和失败分支写进测试名称。
归纳起来,JDK 26 的基本类型模式让模式匹配从“引用类型识别”向“值与类型一起判断”扩展。真正的迁移动作不是把 instanceof Integer 全部替换成 instanceof int,而是先确认安全转换、装箱状态、null 处理和 switch 覆盖范围,再选择更短但仍然可读的表达。
两个常见疑问
基本类型 pattern 会自动处理 null 吗? 不会。输入为 null 时,primitive pattern 不会提供一个可用数值;在 switch 中需要用 case null 明确表达,或在外围先定义空值策略。
启用预览后,所有旧 switch 都要改写吗? 不需要。预览能力是新增表达方式,已有代码可以继续按原语义工作。只有当原代码确实被包装类型分支、手动拆箱和范围判断拖长,且团队能够统一 JDK 26 预览运行条件时,才值得按边界逐段迁移。
-
479 收藏
-
337 收藏
-
128 收藏
-
149 收藏
-
202 收藏
-
485 收藏
-
190 收藏
-
114 收藏
-
134 收藏
-
158 收藏
-
241 收藏
-
345 收藏
-
279 收藏
-
494 收藏
-
320 收藏
-
文章 · java教程 | 1天前 | Java · 集合 · 资源管理 · Stream · Java教程 · supplier Java Stream Files.lines Stream生命周期 重复遍历146 收藏
-
165 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习