DEEPDIVE · LANGUAGE · JEP 12 / 451
预览特性:试用草案,而不是半成品
JDK 21 · 深钻主题 07 / 08 · 预计 40 分钟
一句话预览特性是已经进入 JDK、但尚未永久的语言或 API 改动:必须显式开启,可能在下一个版本调整或移除。JDK 21 同时存在 final、preview、incubator、experimental 四种状态,分清它们是升级不踩坑的前提。
本质:预览不是「提前发布的正式功能」,而是「邀请你试用并提供反馈的草案」。用它可以,但不能假设它跨版本稳定。
学完你要能自己回答
- preview、incubator、experimental 三种状态分别是什么意思?
- 为什么预览特性编译和运行都要加
--enable-preview? - JDK 21 中哪些特性是预览,哪些已经正式?
- 在 Maven/Gradle 项目中试用预览特性要改哪些配置?
- 升级 JDK 时,如何快速判断哪些代码可能受预览特性影响?
01 四种状态:正式菜、试吃菜、后厨菜、实验种子02 三条硬规则
三条硬规则02
- 编译和运行必须同时开启:
javac --enable-preview --release 21,java --enable-preview。只开一边会报错。 - 只能用当前 JDK 的预览特性:用 JDK 21 编译的预览 class 文件,不能拿到 JDK 22 上运行;预览特性不承诺跨版本二进制兼容。
- 不要在生产代码中依赖预览 API:可以试用、做原型、写实验分支,但不要把预览特性作为稳定交付的基础。
# 编译:必须指定 --release,预览特性与目标版本绑定
javac --enable-preview --release 21 Demo.java
# 运行:必须再次开启
java --enable-preview Demo
# 忘记运行参数时:
# UnsupportedClassVersionError: Preview features are not enabled
01 预览特性在 class 文件里长什么样02 JDK 21 的状态清单03 顺带一提:动态加载 Agent 的警告
预览特性在 class 文件里长什么样01
启用了预览特性的 class 文件,minor version 被写成 65535(0xFFFF),major version 仍是目标 JDK 的版本号(JDK 21 为 65)。JVM 看到 65535 就要求 --enable-preview。
javap -v Demo.class | head -5
# minor version: 65535
# major version: 65
- 预览特性与 JDK 版本绑定:JDK 21 的预览 class 不能在 JDK 22 的 JVM 上运行。
--release 21不仅设置 class 版本,还限制可用的 API 和语言特性;不写--release容易误用当前 JDK 的新 API。- JShell 也需要
--enable-preview,例如jshell --enable-preview。
JDK 21 的状态清单02
| 特性 | JEP | JDK 21 状态 | 备注 |
|---|---|---|---|
| 虚拟线程 | 444 | 正式 | 线程模型正式落地 |
| SequencedCollection | 431 | 正式 | 集合两端 API |
| Record Patterns | 440 | 正式 | record 解构 |
| Pattern Matching for switch | 441 | 正式 | 模式 switch |
| 分代 ZGC | 439 | 正式(产品特性) | 需 -XX:+ZGenerational |
| String Templates | 430 | 预览 | 模板表达式 |
| Unnamed Patterns and Variables | 443 | 预览 | 用 _ 忽略不需要的变量 |
| Unnamed Classes and Instance Main Methods | 445 | 预览 | 简化入门程序 |
| Scoped Values | 446 | 预览 | 作用域内不可变上下文 |
| Structured Concurrency | 453 | 预览 | 结构化任务作用域 |
| Foreign Function & Memory API | 442 | 预览(第三轮) | JDK 22 转正 |
| Vector API | 448 | 孵化(第六轮) | 需要 --add-modules jdk.incubator.vector |
这张表只描述 JDK 21 的状态。升级到后续版本时,预览特性可能转正、修改或消失,务必查对应版本页与 JEP 状态。
顺带一提:动态加载 Agent 的警告03
JEP 451 在 JDK 21 中让 JVM 在动态加载 Java Agent 时打印警告,为未来默认禁止做准备。它不是预览特性,而是「准备阶段」的弃用策略。
# 看到这类警告时:
WARNING: A {Java,JVM TI} agent has been loaded dynamically ...
WARNING: If a serviceability tool is in use, please run with -XX:+EnableDynamicAgentLoading ...
# 明确需要时:
java -XX:+EnableDynamicAgentLoading -jar app.jar
这类「准备弃用」的特性与 preview 不同:功能仍然默认可用,但未来版本可能改为默认拒绝。升级时应把它列入检查清单。
01 在构建工具中开启预览02 试用策略:隔离、标记、可回退03 可运行示例:未命名类 + 实例 main
在构建工具中开启预览01
Maven
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<release>21</release>
<compilerArgs>
<arg>--enable-preview</arg>
</compilerArgs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>--enable-preview</argLine>
</configuration>
</plugin>
Gradle
tasks.withType(JavaCompile).configureEach {
options.release.set(21)
options.compilerArgs += "--enable-preview"
}
tasks.withType(Test).configureEach {
jvmArgs += "--enable-preview"
}
- 编译参数和测试运行参数必须成对配置,否则测试会在类加载时失败。
- IDE 的「语言级别」和「JVM 参数」是两处独立设置,只改一处同样会失败。
- CI 中固定 JDK 发行版和版本号(例如 21.0.12),不要用浮动的
21标签。
试用策略:隔离、标记、可回退02
- 隔离:把预览特性封装在独立模块或适配层,业务代码依赖稳定接口。
- 标记:在代码注释和构建脚本里明确标注预览版本与开关,方便升级时搜索。
- 验证:CI 增加一个「预览开启」的 job,以及一个「关闭预览」的基线 job,确保主路径不依赖预览。
- 回退:保留不依赖预览的实现分支;升级前先跑预览相关的编译与测试。
// 用运行时版本做显式检查,避免在不支持的 JDK 上误用
int feature = Runtime.version().feature();
if (feature < 21) {
throw new IllegalStateException("需要 JDK 21+,当前 " + feature);
}
可运行示例:未命名类 + 实例 main03
// HelloPreview.java —— JDK 21 预览特性 JEP 445
void main() {
System.out.println("Hello from " + Runtime.version());
System.out.println("preview demo");
}
javac --enable-preview --release 21 HelloPreview.java
java --enable-preview HelloPreview
Hello from 21.0.12+7-LTS preview demo
去掉 --enable-preview 运行会失败;把 class 文件拿到 JDK 22 上运行也会失败。这两点正是预览特性「与版本绑定」的直接体现。
01 先猜再看02 费曼自检:状态与升级
先猜再看01
先猜再看预览特性会在下一个 JDK 版本自动转正吗?
不会。预览特性需要经过反馈和修订,可能多轮预览后才转正,也可能被修改或移除。是否转正由后续 JEP 决定,不是时间到了自动生效。
先猜再看用 JDK 21 编译的预览 class,能在 JDK 22 上运行吗?
不能。预览 class 的 minor version 为 65535,且与编译它的 JDK 版本绑定;跨版本运行会报错,必须用对应版本重新编译。
先猜再看incubator 模块和 preview 特性是一回事吗?
不是。Incubator 是独立模块(如 jdk.incubator.vector),通过 --add-modules 使用;Preview 是 JDK 核心 API/语言特性,通过 --enable-preview 使用。两者都可能变化,但机制不同。
费曼自检:状态与升级02
01. 为什么预览特性要求编译和运行都开启参数?
编译期需要允许解析预览语法/API,运行期需要 JVM 允许加载 minor version 65535 的类;缺一不可。
02. 如何判断一个 class 文件是否使用了预览特性?
用 javap -v 查看 minor version,65535 表示预览;也可以看构建参数和 JDK 版本。
03. JDK 21 的虚拟线程是预览吗?
不是,虚拟线程在 JDK 21 已正式(JEP 444)。JDK 21 的预览特性包括 String Templates、Scoped Values、Structured Concurrency 等。
04. 生产项目应该如何使用预览特性?
隔离在实验模块/分支,主路径不依赖;固定 JDK 版本;CI 同时验证开启与关闭预览;升级前重新编译并回归。
05. 「准备弃用」与「预览」有什么共同点和区别?
共同点是都提示未来可能变化;区别是预览默认不可用、需要显式开启,准备弃用通常默认仍可用,但未来会改为禁止或移除。
学习进度
记录保存在本机浏览器 · 不上传