《Java 编程入门》附录 C 21→25 版本差异

把 JDK 21 到 25 的正式特性与预览特性分开列清楚:21 的虚拟线程、记录模式、switch 模式匹配,22 的 FFM API 与未命名变量,23 的 Markdown 文档注释与分代 ZGC 默认,24 的 Class-File API 与 Stream Gatherers,25 的 Scoped Values、模块导入声明、紧凑源文件、灵活构造器与紧凑对象头,并给出迁移建议。

怎么读这一页

这一页按版本列出 JDK 21 到 25 的主要变化,重点是正式特性;预览和孵化特性单独标注,提醒你不要在生产代码里依赖。信息来源是 OpenJDK 各版本的 JEP 列表,写作时逐项核对过。版本细节以官方 Release Notes 为准,这里给的是学习与迁移视角的摘要。

先记住三条原则:

  1. 正式(final)才能放心用。 预览特性可能在下一个版本改名、改语义甚至删除。
  2. LTS 才是生产主线。 21 和 25 是 LTS;22、23、24、26、27 是特性版,支持周期短。
  3. 语法新不代表必须用。 团队没升级到 25,就不要在共享库里用 25 独有语法。

版本总览

版本发布时间类型本书角色
JDK 212023-09LTS对照线,编译目标默认
JDK 222024-03特性版仅作版本演进参考
JDK 232024-09特性版仅作版本演进参考
JDK 242025-03特性版卷四会提到 AOT 与 GC
JDK 252025-09LTS主线,开发与运行版本
JDK 262026-03特性版不展开
JDK 272026-09特性版不展开

JDK 21 LTS:虚拟线程与模式匹配

JDK 21 是本书的对照线,也是很多公司的生产版本。它的正式特性主要集中在这几项:

JEP特性影响
431Sequenced CollectionsList、Deque、SortedSet 有了统一的「首/尾」访问接口
439Generational ZGCZGC 支持分代,降低年轻代回收成本
440Record Patterns可以在模式里解构 record
441Pattern Matching for switchswitch 支持类型模式和守卫
444Virtual Threads虚拟线程正式可用
452Key Encapsulation Mechanism API后量子密码迁移的基础 API

卷一用到的 record、sealed、switch 模式匹配、文本块,在 21 里都已正式。下面这段代码在 21 上可以编译运行:

sealed interface Command permits AddBook, BorrowBook { }
record AddBook(String isbn, String title) implements Command { }
record BorrowBook(String isbn, String memberId) implements Command { }

static String describe(Command c) {
    return switch (c) {
        case AddBook(var isbn, var title) -> "新增 " + title;
        case BorrowBook(var isbn, var memberId) -> "借出 " + isbn + " 给 " + memberId;
    };
}

本机用 javac --release 21 编译并运行:

新增 Java
借出 978 给 m-1

21 的预览特性包括 String Templates(JEP 430)、Unnamed Patterns and Variables(442)、Unnamed Classes and Instance Main Methods(443)、Scoped Values(446)、Structured Concurrency(453)、FFM API 第三次预览(442)、Vector API(448)。这些在 21 上都需要 --enable-preview,不要在没有评估的情况下用于生产。

JDK 22:FFM 正式与未命名变量

JDK 22 是特性版,主要变化:

JEP特性状态
423Region Pinning for G1正式
447Statements before super(…)正式
454Foreign Function & Memory API正式
456Unnamed Variables & Patterns正式
458Launch Multi-File Source-Code Programs正式
457Class-File API预览
461Stream Gatherers预览
462Structured Concurrency第二次预览
464Scoped Values第二次预览

对卷一有实际影响的是两项:

  • _ 未命名变量(456 正式):for (var _ : list) 或 catch (Exception _),表示「我不关心这个值」。21 上不可用。
  • super(...) 之前的语句(447 正式):构造器里可以先做参数校验,再调用父类构造器。21 上不可用;25 的 JEP 513 把它扩展成「灵活构造器」。

FFM API 正式意味着 Java 可以更安全地调用本地库,替代大部分 JNI 场景。卷一不涉及,卷三会讲。

JDK 23:Markdown 文档与分代 ZGC 默认

JEP特性状态
467Markdown Documentation Comments正式
474ZGC: Generational Mode by Default正式
471Deprecate sun.misc.Unsafe Memory-Access Methods正式(弃用)
455Primitive Types in Patterns预览
473Stream Gatherers第二次预览
476Module Import Declarations预览
480Structured Concurrency第三次预览
481Scoped Values第三次预览
482Flexible Constructor Bodies第二次预览

两个实际影响:

  • Javadoc 可以写 Markdown(467):/// 开头的文档注释支持 Markdown 语法,文档更好写。
  • ZGC 默认分代(474):用 ZGC 时不再需要显式开分代,低延迟场景的默认行为变了。

sun.misc.Unsafe 的内存访问方法被标记为弃用(471),这是为未来移除做准备。业务代码不应直接用 Unsafe;框架和序列化库会逐步迁移到 VarHandle 和 FFM API。

JDK 24:Class-File API、Stream Gatherers、虚拟线程去固定

JEP特性状态
404Generational Shenandoah实验
450Compact Object Headers实验
472Prepare to Restrict the Use of JNI正式(警告)
475Late Barrier Expansion for G1正式
479Remove the Windows 32-bit x86 Port正式
483Ahead-of-Time Class Loading & Linking正式
484Class-File API正式
485Stream Gatherers正式
486Permanently Disable the Security Manager正式
490ZGC: Remove the Non-Generational Mode正式
491Synchronize Virtual Threads without Pinning正式
493Linking Run-Time Images without JMODs正式
496Quantum-Resistant KEM正式
497Quantum-Resistant DSA正式
487Scoped Values第四次预览
488Primitive Types in Patterns第二次预览
492Flexible Constructor Bodies第三次预览
494Module Import Declarations第二次预览
495Simple Source Files and Instance Main Methods第四次预览
499Structured Concurrency第四次预览

对卷三、卷四最重要的是 491:虚拟线程在 synchronized 块里不再固定(pin)载体线程。 在 21 上,虚拟线程遇到 synchronized 可能会阻塞载体线程,影响吞吐;24 起这个问题基本消失。卷三讲虚拟线程时会展开。

484 的 Class-File API 正式,意味着读写字节码有了官方 API,不必依赖 ASM。卷三会用它做简单的字节码分析。485 的 Stream Gatherers 给 Stream 增加了自定义中间操作,卷一会提一句,不深入。

JDK 25 LTS:Scoped Values、紧凑源文件、灵活构造器

JDK 25 是本书主线。正式特性里,和入门书关系最大的有六项:

JEP特性对卷一的意义
506Scoped Values线程间共享不可变上下文,替代部分 ThreadLocal
510Key Derivation Function API密码学 API,卷一不涉及
511Module Import Declarationsimport module java.base; 一行导入整个模块
512Compact Source Files and Instance Main Methods小例子可以不写 class 和 public static void main
513Flexible Constructor Bodies构造器里先校验、再调 super(...)
519Compact Object Headers对象头从 96/128 位压到 64 位,降低内存
503Remove the 32-bit x86 Port32 位 x86 不再支持
514Ahead-of-Time Command-Line ErgonomicsAOT 缓存更易用
515Ahead-of-Time Method ProfilingAOT 方法分析
518JFR Cooperative SamplingJFR 采样改进
520JFR Method Timing & TracingJFR 方法级计时
521Generational ShenandoahShenandoah 分代

模块导入声明

JDK 25 起,可以一行导入整个模块的公开包:

import module java.base;

public class ModuleImport {
    public static void main(String[] args) {
        List<String> names = List.of("a", "b");
        System.out.println(names);
    }
}

本机输出:

[a, b]

这减少了大量 import java.util.* 之类的样板,但也会让「这个名字来自哪个包」变得不直观。团队项目里是否采用,需要权衡可读性。

紧凑源文件与实例 main

JDK 25 起,小例子可以写成:

void main() {
    System.out.println("compact source file: " + Runtime.version());
}

用 java Hello.java 直接运行(文件名不必与类名一致,因为没有显式类):

compact source file: 25.0.5+8-LTS

这适合教学、脚本和原型;正式项目仍然建议写完整的类结构,否则包、模块、可见性都无从谈起。

灵活构造器

JDK 25 起,构造器里可以先做校验,再调用 super(...):

public class Isbn {
    private final String value;

    public Isbn(String raw) {
        if (raw == null || raw.isBlank()) {
            throw new IllegalArgumentException("isbn");
        }
        super();
        this.value = raw;
    }
}

本机运行:

978-7-111-12345-6
isbn

21 上同样的代码无法编译,因为语句不能出现在 super() 之前。这个特性让「参数校验」和「父类构造」的顺序更自然,也避免了在父类构造器里调用可被重写的方法。

Scoped Values

Scoped Values 提供一种线程内(以及虚拟线程间)共享不可变上下文的方式:

private static final ScopedValue<String> MEMBER_ID = ScopedValue.newInstance();

ScopedValue.where(MEMBER_ID, "m-1").run(() -> {
    System.out.println(MEMBER_ID.get());
});

它比 ThreadLocal 更安全:值不可变、作用域明确、虚拟线程下没有继承开销。卷三会详细讲它和 ThreadLocal 的取舍。

紧凑对象头

JEP 519 把对象头从 96 位(或 128 位)压缩到 64 位,减少堆占用。这是 JVM 层的优化,对代码透明,但会影响对象布局、jol 等工具的读数。卷四讲对象布局时会提到。

迁移建议

从 JDK 21 升到 25,按这个顺序做:

  1. 先升构建工具链。 Maven Compiler Plugin、Surefire、Failsafe、Shade、SpotBugs、JaCoCo 都要支持 25。
  2. 检查依赖。 字节码增强类库(Mockito、Byte Buddy、CGLIB)、序列化库、数据库驱动、容器基础镜像是否声明支持 25。
  3. 跑测试。 重点看并发、日期时间、字符集、反射、Unsafe 相关测试。
  4. 看 GC 日志和启动时间。 25 的默认 GC、紧凑对象头、AOT 缓存都可能改变性能曲线。
  5. 再改代码。 用新语法是最后一步,不要和升级混在一起。

不要做的事:

  • 不要在生产代码里用预览特性,除非你接受每个版本都要改。
  • 不要用 -source/-target 代替 --release。
  • 不要假设「21 能跑,25 一定行为一样」,尤其是 GC、字符集、TLS、日期时间默认值。

特性矩阵

特性2122232425
record / sealed✅✅✅✅✅
switch 模式匹配✅ 正式✅✅✅✅
虚拟线程✅ 正式✅✅✅✅
未命名变量 _❌ 预览✅ 正式✅✅✅
FFM API预览✅ 正式✅✅✅
Markdown 文档注释❌❌✅ 正式✅✅
Class-File API❌预览预览✅ 正式✅
Stream Gatherers❌预览预览✅ 正式✅
虚拟线程去固定❌❌❌✅ 正式✅
模块导入声明❌❌预览预览✅ 正式
紧凑源文件 / 实例 main❌ 预览预览预览预览✅ 正式
灵活构造器❌❌预览预览✅ 正式
Scoped Values预览预览预览预览✅ 正式
紧凑对象头❌❌❌实验✅ 正式
结构化并发预览预览预览预览第五次预览

「预览」表示需要 --enable-preview,且 API 可能在下一版变化;「❌」表示该版本没有这个特性。

常见误区

「25 是 LTS,所以所有库都支持。」 LTS 是 JDK 的支持承诺,不是第三方库的承诺。升级前查依赖的官方兼容性说明。

「预览特性用着没问题,就可以上生产。」 预览特性可能在下一个版本删除或改语义。除非你能接受每个版本跟着改,否则不要用。

「新语法一定更好。」 紧凑源文件适合教学和脚本,不适合正式项目;模块导入声明减少样板,但降低可读性。语法选择要看团队和维护成本。

「21 和 25 只是版本号不同。」 虚拟线程去固定、ZGC 默认分代、紧凑对象头、Unsafe 弃用,这些都会改变运行时行为。升级要按迁移清单走。

小结

  • 21 的正式特性:Sequenced Collections、分代 ZGC、记录模式、switch 模式匹配、虚拟线程。
  • 22 的正式特性:FFM API、未命名变量、super(...) 前语句、多文件源码启动。
  • 23 的正式特性:Markdown 文档注释、ZGC 默认分代、Unsafe 内存访问弃用。
  • 24 的正式特性:Class-File API、Stream Gatherers、虚拟线程去固定、AOT 类加载、后量子密码 API。
  • 25 的正式特性:Scoped Values、模块导入声明、紧凑源文件、灵活构造器、紧凑对象头、JFR 改进。
  • 卷一以 25 为主线、21 为对照;编译目标默认 21,语法只用 21 也成立的部分。
  • 迁移顺序:工具链 → 依赖 → 测试 → 观测 → 再改代码。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「java」更多文章

  1. 《Netty 高性能网络编程》15.3 linkd-v2 验收与复盘
  2. 《Netty 高性能网络编程》15.2 限流、超时与降级
  3. 《Netty 高性能网络编程》15.1 网关路由与转发