升到 JDK 27,instanceof/switch 可写原始类型 pattern(JEP532 第五预览);按 exact 转换匹配,仍须 --enable-preview。
同系列:PEM 编解码、TLS 后量子混合、LazyConstant、结构化并发------一句对照,不写进本稿交付。
痛点:原始类型还得先强转,再猜会不会丢精度?
现场三类卡点:
instanceof过去不能测原始类型 ------int能否安全落进byte,只能自己比范围或强转后再比对;写错就静默截断,线上要靠单测或日志才发现。switch选不了long/float/double/boolean------浮点分支只好拆成 if-else;布尔穷尽也写不成 switch,评审时常被问「为什么不用模式匹配」。- 嵌套 record 里
double想当int用 ------旧写法必须JsonNumber(double a)再手动(int)a,窄化是否丢信息全靠人眼,和「先 instanceof 再强转」是同一类负担。
目标很具体:在 JDK 27 preview 上,用原始类型 pattern / instanceof / switch,按 exact conversion 写通匹配与绑定;并记住 float case 常量必须写成 0f。

图注:值进入 exact 转换判定------能无损落到目标类型则匹配并绑定,否则走下一分支;编译与运行都要带 --enable-preview。
JDK 27 交付了什么?JEP 532 第五预览
口径以 JEP 532、JDK 27 项目页、Oracle JDK 27 Release Notes · Language 为准(核对日 2026-10-24 Asia/Shanghai):
| 项 | 口径 |
|---|---|
| 状态 | Closed / Delivered ,Release = 27 |
| 轮次 | Fifth Preview(标题:Primitive Types in Patterns, instanceof, and switch) |
| 相对 JEP 530 | without change(530 已收紧 unconditional exactness 与 switch dominance;532 无再改语义) |
| GA | JDK 27 于 2026-09-15 达 General Availability |
| 开关 | javac --release 27 --enable-preview 与 java --enable-preview |
| 勿写 | 「已转正 / 无需 preview」------官方仍标 preview language feature |
历史一句:JEP 455(JDK 23)提出,488(24)、507(25)无改再预览,530(26)做了两处收紧后,532 第五次预览且相对 530 无变更。Closed/Delivered 只表示该 JEP 随 27 进了发行版;交付物本身仍是预览语言特性------javac 开了、java 没开,或反过来,都会一边过、一边挂。
JLS preview 文档(可到达)Changelog 写明:2026-05-04 为 Java SE 27 第五预览刷新,无规范性变更。创作优先引 JEP 例句,JLS 作边界复核即可。
bash
# 编译(两边都要带 --enable-preview)
javac --release 27 --enable-preview PrimitivePatternDemo.java
# 运行
java --enable-preview PrimitivePatternDemo
# 或源码启动 / jshell
java --enable-preview PrimitivePatternDemo.java
jshell --enable-preview
团队规范若禁止 preview,本稿只能当调研与 PoC 清单,不能当「升完 27 就全仓替换手写范围判断」的通行证。
instanceof:只测 vs 测并绑定
右侧是 Type (非 Pattern)时,只做安全转换前置检查,不自动转换,仍要传统 cast:
java
// 需 --enable-preview(javac --release 27 --enable-preview / java --enable-preview)
public class InstanceofTypeTest {
public static void main(String[] args) {
int i = 42;
if (i instanceof byte) { // 值能无损落进 byte
byte b = (byte) i; // 仍须传统强转
System.out.println(b);
}
}
}
右侧写成 pattern (instanceof T t)时:测试 + 条件转换,成功则绑定:
java
// 需 --enable-preview
public class InstanceofPatternBind {
public static void main(String[] args) {
int i = 42;
if (i instanceof byte b) {
System.out.println(b); // 已绑定,无需再 (byte)i
}
}
}
语义就是 JEP 原话方向:「test if i instanceof byte and, if so, cast i to byte and bind that value to b」 。没有新增「另一种转换种类」------Non-Goals 写明 not a goal to add new kinds of conversions ;依据仍是 exact conversion (无信息损失则报告 true)。
实务上优先写 pattern 绑定:少一次手写 cast,也少一次「测过了却忘了转」的疏漏。若只想做守卫、后面仍要走旧强转路径,再单独用类型测试形态。
exact 转换:42 能进 byte,1000 不能
运行时用 exact conversion 判定。JEP 例句(可本地改数字复验):
java
// 需 --enable-preview
public class ExactConversionDemo {
public static void main(String[] args) {
int a = 42;
System.out.println(a instanceof byte); // true(exact)
int b = 1000;
System.out.println(b instanceof byte); // false(not exact)
int c = 16_777_217; // 2^24 + 1
System.out.println(c instanceof float); // false(not exact)
System.out.println(c instanceof double); // true(unconditionally exact)
}
}
| 左侧值 | 右侧类型 | 结果 | 原因(口径) |
|---|---|---|---|
42 |
byte |
true | exact,无损 |
1000 |
byte |
false | 超出 byte 范围,not exact |
2^24+1 |
float |
false | float 尾数装不下,not exact |
2^24+1 |
double |
true | unconditionally exact |
2^24+1 这组最容易误判:人直觉觉得「int 比 float 窄,应该总能进 float」,但 float 有效位数约 24 bit,超过就会丢精度,因此 not exact ;进 double 则 unconditionally exact。写作时把「instanceof byte 只测」和「instanceof byte b 测并绑定」分开讲,避免读者以为类型测试形态已经把变量变成了 byte。
switch:long / float / double / boolean
JEP 把 switch 选择器扩到剩余原始类型:long、float、double、boolean(及对应装箱类型)。硬约束:case 常量类型必须与选择器相同 (或对应装箱)。选择器是 float / Float 时,常量必须是 float 字面量------下面这段合法,但若把 0f 误写成 0 会非法:
java
// 需 --enable-preview
public class SwitchFloatDemo {
static float classify(float v) {
return switch (v) {
case 0f -> 5f;
case float x when x == 1f -> 6f + x;
case float x -> 7f + x;
};
}
public static void main(String[] args) {
System.out.println(classify(0f));
System.out.println(classify(1f));
System.out.println(classify(2.5f));
}
}
浮点 case 按 representation equivalence 判重;表示等价的两个字面量会撞成重复 label(例如 1.0f 与 0.999999999f)。写字面量时别「近似相等就当同一个 case」------编译器按表示等价,不是按十进制肉眼。
boolean 列全 true / false 即穷尽,再加 default 非法:
java
// 需 --enable-preview:boolean 穷尽示例
public class SwitchBooleanDemo {
static String label(boolean ok) {
return switch (ok) {
case true -> "yes";
case false -> "no";
// 不可再加 default:已穷尽
};
}
public static void main(String[] args) {
System.out.println(label(true));
System.out.println(label(false));
}
}
long 选择器同理,case 常量用 long 字面量(带 L)或可判定为同类型的常量;别把 int 字面量随手塞进 switch (long) 的 case 里指望隐式放宽------规则要求类型对齐。

图注:switch 选择器扩至 long/float/double/boolean;float 分支常量必须 0f 而非 0,类型与选择器对齐。
嵌套 record:JsonNumber(int a) 何时匹配?
旧写法嵌套原始类型 pattern 须与组件类型完全一致 (JsonNumber(double a)),再手动 (int)a。JEP 允许在组件是 double 时直接写 int a------当且仅当 该 double 能无损转为 int 才匹配:
java
// 需 --enable-preview
sealed interface JsonValue permits JsonObject, JsonString, JsonNumber {}
record JsonObject(java.util.Map<String, JsonValue> map) implements JsonValue {}
record JsonString(String s) implements JsonValue {}
record JsonNumber(double d) implements JsonValue {}
public class JsonAgePattern {
static void printAge(JsonValue json) {
if (json instanceof JsonObject(var map)
&& map.get("name") instanceof JsonString(String n)
&& map.get("age") instanceof JsonNumber(int a)) {
System.out.println(n + " age=" + a);
} else {
System.out.println("no exact int age");
}
}
public static void main(String[] args) {
var ok = new JsonObject(java.util.Map.of(
"name", new JsonString("Ada"),
"age", new JsonNumber(30.0)));
var bad = new JsonObject(java.util.Map.of(
"name", new JsonString("Bob"),
"age", new JsonNumber(30.5)));
printAge(ok); // Ada age=30
printAge(bad); // no exact int age
}
}
匹配失败分支必须保留------不是「任意嵌套深度总能自动窄化」。JSON 年龄字段若可能带小数,就要在 else 里显式处理,或退回 JsonNumber(double a) 再自行决定舍入策略。
Dominance:一句源不兼容提醒
无条件 pattern 之后的死 case,现在会在编译期报错;这构成对语言的 source-incompatible 变化------以前能编过、但易误导维护者的 switch,升级后可能直接失败。JEP 530 已引入 tighter dominance;532 相对 530 无再改 。边界有限:主要冲击「无条件模式后再写其它 case」以及「常量已被前置类型模式无条件覆盖」的少数 switch,勿写成「升级后 pattern/switch 大面积无法编译」。
典型边界(selector 类型为接口 A 时,case A _ 无条件匹配,其后 case B _ 被 dominate):
java
// 需 --enable-preview;演示 dominance,勿当业务模板
interface A {}
interface B {}
public class DominanceDemo {
static void demo(A a) {
switch (a) {
case A _ -> { /* unconditional for selector A */ }
// case B _ -> { } // 编译错误:dominated
}
}
public static void main(String[] args) {
demo(new A() {});
}
}
实务上:升 27 后若旧 switch 报「dominated」,先删掉无条件 pattern 后面的死分支,而不是关掉 preview 硬扛。多数业务代码没有「无条件 pattern 后再挂一堆 case」这种写法;真正撞上的,按编译器提示收敛即可。
怎么验?
按下面清单自测,比只看 JEP 截图踏实:
- 开关成对 :
javac --release 27 --enable-preview与java --enable-preview都开;只开一边会一边过、一边挂。 - exact 三组 :
42 instanceof byte→ true;1000 instanceof byte→ false;16_777_217 instanceof float→ false、instanceof double→ true。 - float case :
switch (float)里写case 0→ 应编译失败 ;改case 0f→ 通过。 - boolean 穷尽 :
true/false列全后再加default→ 应编译失败。 - 嵌套 JsonNumber :
30.0→ 匹配int a;30.5→ 不匹配,走 else。 - 别误验:不要把 PEM / TLS / LazyConstant / 结构化并发的 API 拿来证明本稿能力------那些是同发版其它 JEP,见文首内链。
和旧代码怎么并存?
升 27 不必立刻全局替换 if-else。旧「先范围判断再强转」在 preview 关闭的模块里仍是唯一路径;新模块、已开 --enable-preview 的实验仓,可优先用 instanceof byte b 与 float/boolean switch。迁移建议:
- 先改测试与校验逻辑里「数值能否无损落进更窄类型」的分支------失败成本低,也方便对照 exact 表。
- 再动手写协议解析 / DTO 归一化;生成代码与第三方库未声明 preview 前,保持旧写法。
- Code review 清单加两条:float case 是否写成了整数
0;无条件 pattern 后是否还挂着死 case。 - CI 若混用多 JDK,把
--enable-preview收进明确的 preview profile,避免「本机过、流水线挂」。
同发版的 PEM、TLS 混合、LazyConstant、结构化并发已有专文,本稿只守住「原始类型 pattern + exact + switch 扩展」这条线。
常见误读 FAQ
Q:Closed/Delivered 是不是等于可以关 preview?
不是。Closed/Delivered 表示该 JEP 随 JDK 27 交付进发行版;特性仍标 Fifth Preview,官方给出的编译/运行命令都带 --enable-preview。
Q:相对 JDK 26 的 JEP 530,27 又改了什么语义?
without change。530 已增强 unconditional exactness 定义并收紧 switch dominance;532 第五次预览,无再改。
Q:instanceof byte 会不会自动把变量变成 byte?
不会。类型测试形态只报告能否 exact 转换,强转仍要自己写;要绑定请用 instanceof byte b。
Q:float 的 case 0 为什么不行?
选择器是 float 时,case 常量必须是 float 字面量(或对应装箱)。写成 0 是 int 常量,类型不匹配,非法;应写 0f。
Q:嵌套 JsonNumber(int a) 失败是不是编译错误?
不是。匹配失败走运行时下一分支(例如 else);只有当 double→int exact 时才进入绑定。务必保留失败路径。
和装箱类型怎么对齐?
选择器或 instanceof 左侧既可以是原始类型,也可以是对应装箱类型;JEP 明确 switch 覆盖 long/float/double/boolean 以及对应 boxed types。写作时记住两条:
- case 常量类型仍要对齐 :
Float选择器上的常量同样必须是 float 字面量(如0f),写成整数0一样非法。 - 穷尽规则看 unconditional exactness :包装类型
W(对应原始类型P)上,若某类型 pattern 对Punconditionally exact,则可穷尽W(null进 remainder)。例如Byte上仅case int p一类 unconditionally exact 的 pattern 即可穷尽------这是 JEP 摘要口径,主文不必展开成长证明。
日常业务更常碰到的是「原始 int/long 进窄类型」和「float/boolean switch」;装箱路径先保证常量类型与 null 余项想清楚,再谈穷尽表。
完整可编译串联示例
把类型测试、pattern 绑定、float switch 收进一个小类,便于本地 javac --release 27 --enable-preview 后 java --enable-preview:
java
// 需 --enable-preview(javac --release 27 --enable-preview / java --enable-preview)
public class PrimitivePatternDemo {
static String narrow(int i) {
if (i instanceof byte b) {
return "byte:" + b;
}
return "too-wide:" + i;
}
static float classify(float v) {
return switch (v) {
case 0f -> 5f;
case float x when x == 1f -> 6f + x;
case float x -> 7f + x;
};
}
public static void main(String[] args) {
System.out.println(narrow(42)); // byte:42
System.out.println(narrow(1000)); // too-wide:1000
System.out.println(classify(0f));
System.out.println(classify(1f));
int wide = 16_777_217;
System.out.println(wide instanceof float); // false
System.out.println(wide instanceof double); // true
}
}
期望:42 走 byte 绑定;1000 走 too-wide;float 0f/1f 分别进对应分支;2^24+1 对 float 为 false、对 double 为 true。任一步不符,先查是否漏了 --enable-preview,再查 case 是否误写成 0。
为什么不该手写范围常量?
旧代码常见:
java
if (i >= Byte.MIN_VALUE && i <= Byte.MAX_VALUE) {
byte b = (byte) i;
// ...
}
对 byte/short/char 这种「边界清楚」的整数窄化,手写范围大多能对;但一到 int → float、long → double,边界就不再是「最大值最小值」那么简单------尾数位数、次正规数、表示等价都会让「看起来在范围内」的值其实 not exact 。JEP 把判定收进语言:instanceof / pattern 按 exact conversion 说话,比散落各处的魔法常量更不容易写歪。
这也解释了为什么 16_777_217 instanceof float 是 false:不是「int 比 float 小所以一定能转」,而是「转完再转回能否无损」。单测里把 JEP 三组例句钉死,比自己发明一组边界值省心。
预览特性怎么跟生产节奏对齐?
第五预览、相对 530 无变更,意味着语义已连续多轮稳定,但平台仍要求显式开关。常见落地方式:
- 实验模块 / 原型仓 :
pom/build.gradle里单独 profile 打开--enable-preview,与主仓隔离。 - 主仓:继续用手写范围或工具方法;等特性转正(未来 JEP 若 Final)再全局替换------本稿不预测转正版本。
- 文档:README 写明「本目录示例需 JDK 27 + enable-preview」,避免同事用 25/26 裸跑报奇怪语法错。
评审口径建议统一成:「JEP 532 第五预览,Closed/Delivered for 27,相对 530 without change,必须 enable-preview」------比「JDK27 新语法」六个字清楚。
升级清单(给评审贴一条)
把下面几条贴进 JDK 27 语言预览评审即可,不必另开长文:
- 模块/流水线明确声明
--enable-preview,编译与运行成对出现。 - 数值窄化优先
instanceof T t,少用手写范围常量;对照 exact 表写单测。 - float/double switch:case 字面量带
f/d;禁止「看着像 0 就写 0」。 - boolean switch:列全
true/false后不要再加default。 - 嵌套 record 窄化:保留匹配失败分支;小数年龄等场景勿假设总能进
int。 - 编译若报 dominated:删无条件 pattern 后的死 case,视为 530/532 收紧后的预期行为。
- 勿把本稿写成 PEM/TLS/LazyConstant/结构化并发的替代交付------那些已有专文内链。
小结
| 点 | 结论 |
|---|---|
| 改了什么 | 原始类型进入 pattern / instanceof / switch 全上下文 |
| 状态 | JEP 532 Fifth Preview;Closed/Delivered for 27;相对 530 without change |
| instanceof | instanceof byte 只测;instanceof byte b 测并绑定 |
| 判定 | exact conversion;42→byte true,1000→byte false |
| switch | 支持 long/float/double/boolean;float 常量必须 0f |
| 嵌套 | JsonNumber(int a) 仅当 double→int exact |
| 开关 | --enable-preview(javac --release 27 + java) |
| 勿抢 | PEM / TLS PQ / LazyConstant / Structured Concurrency |
升到 27 后,原始类型匹配能写进语言正面语法。守住 exact 与 case 常量类型两条红线,并接受仍是第五预览,就能把「先猜再强转」换成可核对的 pattern。本地用 JEP 三组 exact 例句加 float 0f 负例跑一遍,比只改业务分支踏实。其它 JDK 27 能力见前述内链,本文不展开。