📑 Java 演进系列总览 | Java 8 | Java 11 | Java 17 | Java 21 | Java 25

Java 25 于 2025 年 9 月 16 日 GA,是继 Java 21 之后的 LTS 版本,按 Oracle 的支持路线图获得长期支持。它的主线是生产力与运行时间的双线演进:语言层面定型了紧凑源文件、模块导入声明与灵活构造器主体,把"写一个可运行 Java 程序"的样板成本降到历史最低;API 层面补齐了作用域值与标准密钥派生函数接口;HotSpot 则交付了紧凑对象头、分代 Shenandoah 与 AOT 命令行人机工程学。更全面的特性盘点可参阅Java 25 特性专题

紧凑源文件与实例 main 方法

JEP 512(正式特性,历经 JEP 445、463、477、495 四轮预览后定稿)允许省略 class 声明与 public static void main(String[] args) 样板:未显式声明类的源文件会得到一个隐式声明的类,成员可保留实例语义,java.io.IO 按需自动导入。

1
2
3
4
// Hello.java:完整可运行的 Java 程序
void main() {
IO.println("Hello, Java 25!");
}
1
java Hello.java

main 方法还支持实例形式:非静态、参数可省略、字段是实例字段,程序状态直接落在对象上,适合教学与小工具。

1
2
3
4
5
6
7
8
public class Greeter {
private final String name = "Java 25";

// 实例 main:由 JVM 创建实例后调用
void main() {
IO.println("Hello, " + name + "!");
}
}

旧的静态 main 签名完全兼容不受影响;简洁写法只影响新代码的表达方式。

模块导入声明

JEP 511(正式特性)新增 import module:一条语句导入目标模块导出的全部包。以 import module java.base; 为例,ListMapExecutor 等常用类型无需再逐包导入。

1
2
3
4
5
6
import module java.base;

void main() {
var langs = List.of("Java", "25");
langs.forEach(l -> IO.println(l));
}

模块导入可与普通单类型导入、按需导入混用;当两个模块导出同名简单类型时,冲突仍需显式普通导入消歧。依赖第三方模块时,模块导入同样适用,前提是该模块已在类路径或模块路径上。

灵活构造器主体

JEP 513(正式特性)允许在 super(...) 调用之前编写语句——此前"先校验参数、快速失败"只能放在 super 之后,或改用静态工厂绕开。提前语句的限制是不得访问未构造完成的实例:不能引用 this、实例字段或实例方法;但可以校验参数、读取静态成员、准备局部变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
abstract class AbstractRange {
protected final int low, high;

AbstractRange(int low, int high) {
this.low = low;
this.high = high;
}
}

public class Range extends AbstractRange {
public Range(int low, int high) {
if (low > high) { // super 之前完成参数校验
throw new IllegalArgumentException(
"low > high: " + low + " > " + high);
}
super(low, high); // 校验通过才委托父类构造
}
}

价值在于"失败早于副作用":父类构造器可能已做日志、注册等有副作用的工作,提前校验保证无效参数绝不触发它们。

作用域值

JEP 506(正式特性)定型了 ScopedValue:不可变、生命周期严格绑定语法作用域的上下文传递机制,目标是替代 ThreadLocal。值通过 ScopedValue.where(...).run(...) 绑定,仅在 run 的词法范围内可见,结束即自动清理,无需 remove,也不会泄漏到线程池中的下一个任务。

1
2
3
4
5
6
7
8
9
10
final ScopedValue<String> USER = ScopedValue.newInstance();

void main() {
ScopedValue.where(USER, "alice").run(() -> service());
}

void service() {
// 词法作用域内隐式获取,无需参数层层透传
IO.println("current user: " + USER.get());
}

作用域内可以再次 where(...).run(...) 重绑定以临时遮蔽外层值。与虚拟线程、结构化并发配合时,作用域值会被 fork 出的子任务自动继承,且绑定开销远低于 ThreadLocal 的线程本地存储表——迁移建议:只读上下文用 ScopedValue,确需可变线程状态才保留 ThreadLocal。

密钥派生函数 API

JEP 510(正式特性)为密钥派生函数(KDF)提供标准 API:新类 javax.crypto.KDF 负责"实例化 + 派生"两步,随 API 附带 HKDF 实现(RFC 5869)。它补上了此前只能靠 SecretKeyFactory 勉强表达 KDF 的缺口,也是 KEM(JEP 452)之后向 HPKE、后量子和混合加密演进的第二块拼图。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import module java.base;
import javax.crypto.KDF;
import javax.crypto.SecretKey;
import javax.crypto.spec.HKDFParameterSpec;

void main() throws Exception {
KDF hkdf = KDF.getInstance("HKDF-SHA256");

byte[] ikm = "input keying material".getBytes();
byte[] salt = "per-deployment salt".getBytes();

// extract + expand:派生 32 字节 AES 密钥
var params = HKDFParameterSpec.ofExtract()
.addIKM(ikm)
.addSalt(salt)
.thenExpand("aes-key".getBytes(), 32);

SecretKey key = hkdf.deriveKey("AES", params);
IO.println("derived key bytes: " + key.getEncoded().length);
}

deriveData(...) 变体返回裸字节数组,用于需要熵而非密钥对象的场景。PBKDF2 不在迁移范围内:它仍走既有的 SecretKeyFactory 通道。

预览与孵化特性

Java 25 仍有一批预览与孵化特性,API 形态未定稿,进入生产前必须评估后续版本的不兼容成本。命令行必须显式开启:

1
2
3
4
5
6
# 预览特性:编译与运行均需 --enable-preview
javac --enable-preview --source 25 Demo.java
java --enable-preview Demo

# 孵化模块:需显式添加
java --add-modules jdk.incubator.vector VectorDemo.java
  • 原始类型模式(JEP 507,第三次预览)instanceofswitch 支持原始类型模式,装箱值自动拆箱参与匹配。
1
2
3
4
5
// 运行:java --enable-preview --source 25 Demo.java
Object value = 42;
if (value instanceof int i) {
IO.println("int 值: " + i); // Integer 拆箱匹配 int
}
  • 稳定值(JEP 502,预览)StableValue 延迟初始化且值不可变,JVM 可据此做常量折叠等优化。
1
2
3
4
5
6
7
// 运行:java --enable-preview --source 25 App.java
final StableValue<Config> CONFIG = StableValue.of();

Config cfg() {
// 仅首次调用执行加载,此后返回稳定值
return CONFIG.computeIfUnset(() -> Config.load());
}
  • 结构化并发(JEP 505,第五次预览)StructuredTaskScope.open() 把并发子任务的生命周期约束在语法作用域内,任一失败即取消其余。
1
2
3
4
5
6
7
8
9
// 运行:java --enable-preview --source 25 App.java
Response handle() throws Exception {
try (var scope = StructuredTaskScope.open()) {
var user = scope.fork(() -> findUser());
var order = scope.fork(() -> fetchOrder());
scope.join();
return new Response(user.get(), order.get());
}
}
  • Vector API(JEP 508,第十次孵化)jdk.incubator.vector 模块中的 SIMD 向量计算 API,依赖 Valhalla 的值对象落地后才会转正,目前仅供评估性能收益。

HotSpot 运行时改进(HotSpot/JDK)

以下均为 HotSpot/JDK 实现层面的能力,不是 Java SE 语言或 API 保证,可用性取决于具体发行版与虚拟机选项。

  • 紧凑对象头(JEP 519):64 位平台上对象头由 12/16 字节压缩到 8 字节,对象密集型负载的堆占用显著下降(SPECjbb2015 实测堆省约 22%)。非默认布局,需显式开启:
1
java -XX:+UseCompactObjectHeaders App
  • 分代 Shenandoah(JEP 521):JDK 24 以实验特性引入的分代模式在 Java 25 转正,按分代假设单独回收年轻代,改善吞吐与内存利用率,同时保持低停顿目标。非默认模式:
1
java -XX:+UseShenandoahGC -XX:ShenandoahGCMode=generational App
  • AOT 命令人机工程学(JEP 514):在 JEP 483 两步工作流(record 训练、create 生成)之上,提供一步生成 AOT 缓存的简化入口,加速应用启动:
1
2
3
4
5
# 一步完成训练运行并生成 AOT 缓存
java -XX:AOTCacheOutput=app.aot -cp app.jar com.example.App

# 生产运行:类加载与链接直接命中缓存
java -XX:AOTCache=app.aot -cp app.jar com.example.App

常用特性总结

  • 紧凑源文件void main() { ... } + 自动导入的 IO,小工具与脚本零样板。
  • 实例 main 方法:非静态入口,程序状态即实例字段。
  • 模块导入声明import module java.base; 一次导入整模块的导出包。
  • 灵活构造器主体super(...) 之前即可校验参数、快速失败。
  • 作用域值ScopedValue.where(...).run(...) 替代 ThreadLocal 的只读上下文传递。
  • KDF APIKDF.getInstance("HKDF-SHA256") 标准化派生密钥。
  • 预览/孵化:原始类型模式、稳定值、结构化并发需 --enable-preview;Vector API 需 --add-modules jdk.incubator.vector
  • HotSpot/JDK:紧凑对象头、分代 Shenandoah、AOT 缓存改善内存占用与启动速度。

迁移与兼容性

从 Java 21 升级到 Java 25 通常是低风险的:语言与 API 变化以新增为主,无破坏性语法变更,旧的静态 main 入口完全兼容。需要注意的点:模块导入与逐包导入可共存,同名冲突时以显式普通导入消歧;构造器提前语句禁止访问未构造实例,迁移校验逻辑时保持纯函数式写法,不碰 this 与实例成员;ScopedValue 不可变,依赖 ThreadLocal 可变写线程的存量代码需要重构而非平移。预览特性(原始类型模式、稳定值、结构化并发)与孵化的 Vector API 仍在演进,生产采用前应接受 --enable-preview 的锁定成本并跟踪后续版本变化。紧凑对象头、分代 Shenandoah、AOT 缓存属于部署调优项,不应写入依赖 Java SE 规范的代码假设。

参考资料