Java 25新增的特性
本文由博主提供思路、结构、行文风格,AI负责搜索资料、生成内容,博主再进行修订、验证。文中的源代码,均已通过验证。
- Java 25新增的特性
- 引言
- 1. 紧凑源文件与实例main方法 (Compact Source Files and Instance main Methods) [JEP 512]
- 2. 模块导入声明 (Module Import Declarations) [JEP 511]
- 3. 灵活构造函数体 (Flexible Constructor Bodies) [JEP 513]
- 4. 作用域值 (Scoped Values) [JEP 506] - 暂未修订
- Java 25的其他主要改进
- 提前编译命令行人机工程学 (Ahead-of-Time Command-Line Ergonomics) [JEP 514]
- 提前编译方法分析 (Ahead-of-Time Method Profiling) [JEP 515]
- 紧凑对象头 (Compact Object Headers) [JEP 519]
- 密钥派生函数API (Key Derivation Function API) [JEP 510]
- 推荐阅读
引言
Java 25于2025年9月发布,是继Java 21之后的又一个长期支持(LTS)版本。Java 25包含18个JEP,其中多项语言特性在经过多轮预览后正式定型。语言层面的永久特性包括:紧凑源文件与实例main方法(JEP 512)、模块导入声明(JEP 511)、灵活构造函数体(JEP 513)。核心库层面正式定型的有作用域值(JEP 506)。此外,紧凑对象头等性能改进也从实验性转为产品级特性。本文将介绍这些正式特性,并提供可运行的示例代码。
1. 紧凑源文件与实例main方法 (Compact Source Files and Instance main Methods) [JEP 512]
紧凑源文件允许编写不包含类声明的Java程序,并支持将main方法定义为实例方法。它的核心目标是让初学者和脚本编写者能用更少的样板代码写Java程序,同时确保这些代码可以无缝地演进为普通的Java类。该特性从JDK 21开始经过四轮预览,最终在JDK 25正式定型。
适用场景:大量独立的小程序、脚本或原型。
传统Java程序的Hello World写法:
public class HelloWorld { public static void main(String[] args) { System.out.println("Hello, World!"); } }在Java 25中,可以直接写:
void main() { IO.println("Hello, World!"); }这里有几个关键变化:
隐式声明的类:当你在源文件里直接写方法(比如
main),编译器会自动把这些内容归属于一个隐式的、未命名的顶层类。你不再需要显式地写出public class ...。实例
main方法:main方法不再必须是static的。它可以是实例方法,Java启动器会在调用它之前自动创建类的实例。新的
IO类:Java 25在java.lang包中新增了IO类,提供了println、print、readln等静态方法。它基于System.out和System.in实现。核心库的隐式导入:紧凑源文件会自动访问
java.base模块导出的所有公共顶层类,这意味着你可以直接使用List、ArrayList、File等类,而不用写import java.util.*;这样的语句。
注意:虽然IO类本身因为位于java.lang包而被隐式导入,但它的静态方法不会被自动导入。你仍然需要写IO.println(...),而不能直接写println(...)。官方的说明是,这样做是为了让紧凑源文件在转化为常规类时过渡更平稳,避免引入额外的静态导入要求。
可运行示例(需保存为CompatSourceFileTest.java):
String greeting() { // 实例方法 return "Hello, World!"; } static String sayHi() { // 静态方法 return "Hi"; } void main() { // 这个是实例方法,而不是static方法 IO.println("Hello World"); IO.println(greeting()); // 无需创建对象 IO.println(sayHi()); // 无需创建对象 // 注意:因为是紧凑源文件,会自动导入java.base模块, // 因此无需import java.util.*就可运行如下代码 List<String> list = new ArrayList<String>(); list.add("a"); IO.println(list.get(0)); }补充说明
紧凑源文件代表的是一个独立的、完整的程序入口,而不是一个可被其他代码复用的库组件。当创建一个紧凑源文件的时候,请注意:
- 每个紧凑源文件都包含一个 main 方法,代表一个程序。
- 它不应该被其他类引用,因此不能用来定义 API。
- 虽然它不能使用package,但是依然可以把它放在不同的子目录中。但在IDEA中,运行该文件,其生成的.class文件会被强制创建在项目的根目录,而非你所在的包路径下。
- 紧凑源文件(Compact Source File)被设计为自动导入 java.base 模块的所有公开类。java.util 包属于 java.base 模块。
2. 模块导入声明 (Module Import Declarations) [JEP 511]
模块导入声明允许通过一条import module语句,导入整个模块导出的所有包,无需逐包编写import语句。需要注意的是,这是新增的一种可选语法,而不是对旧语法的替换。你仍然可以继续使用import java.util.*;这种传统写法。
例如,原来需要三条按需导入声明来引入java.util、java.util.function和java.util.stream这三个包:
import java.util.*; import java.util.function.*; import java.util.stream.*;现在只需一条模块导入声明即可:
import module java.base;import module java.base会导入java.base模块导出的所有包。如果需要使用SQL相关的API,还可以再添加import module java.sql;,它会同时导入java.sql包以及java.sql模块间接导出的包。
需要注意的是,使用模块导入声明时,如果不同包中存在同名的类,可能会导致编译错误,此时可以使用单类型导入声明来消除歧义。
可运行示例(需保存为ModuleImportDeclarationsTest.java):
import java.util.stream.Collectors; import java.util.stream.Stream; public class ModuleImportDeclarationsTest { void main() { var fruits = Stream.of("apple", "banana", "cherry") .map(String::toUpperCase) .collect(Collectors.toList()); IO.println(fruits); } }3. 灵活构造函数体 (Flexible Constructor Bodies) [JEP 513]
在Java 25之前,构造函数中super()或this()调用必须是第一条语句。灵活构造函数体解除了“构造函数中第一条语句必须是 super() 或 this()”的限制。
灵活构造函数体将构造函数体分为两个阶段:序言(prologue)在父类构造器调用前执行(即,super(...)与this(...)调用之前),尾声(epilogue)在其后执行(即,super(...) 或 this(...) 调用之后的所有代码)。序言中可以执行参数校验、参数准备和字段初始化等操作。通过这种方式,即使方法被重写,也能确保对象在对外可见前已经被安全初始化。
public class FlexibleConstructorBodyTest { void main() { try { new PositiveNumber(-1); } catch (IllegalArgumentException e) { IO.println("创建失败: " + e.getMessage()); } var num = new PositiveNumber(42); IO.println("创建成功: " + num.value()); } class PositiveNumber extends Number { private final int value; PositiveNumber(int value) { // ===== 序言:在 super() 之前执行 ===== // Java 25 之前,这里不能写任何语句,必须先调用 super() if (value <= 0) { throw new IllegalArgumentException("value must be positive"); } super(); // 显式调用父类构造器(Number 的无参构造) // ===== 尾声:在 super() 之后执行 ===== this.value = value; } int value() { return value; } @Override public int intValue() { return value; } @Override public long longValue() { return value; } @Override public float floatValue() { return value; } @Override public double doubleValue() { return value; } } }4. 作用域值 (Scoped Values) [JEP 506] - 暂未修订
作用域值允许方法在同一线程内与其被调用者以及子线程之间共享不可变数据,是线程局部变量(ThreadLocal)的现代替代方案。
与ThreadLocal相比,作用域值具有以下优势:数据不可变,避免了并发风险;生命周期由作用域限定,超出作用域后数据自动不可访问;与虚拟线程配合使用时资源开销更低。官方说明指出,作用域值“更容易推理,并且具有更低的空间和时间成本”。
可运行示例(需保存为ScopedValueDemo.java):
import java.lang.ScopedValue; static final ScopedValue<String> USER = ScopedValue.newInstance(); void main() { ScopedValue.where(USER, "Alice").run(() -> { IO.println("当前用户: " + USER.get()); callService(); }); } static void callService() { IO.println("服务层获取用户: " + USER.get()); }运行方式:java ScopedValueDemo.java
程序输出:
当前用户: Alice 服务层获取用户: AliceScopedValue.where(USER, "Alice").run(...)将USER绑定到值"Alice",并在该作用域内执行代码。在作用域内调用的任何方法都可以通过USER.get()获取该值,无需通过方法参数逐层传递。
Java 25的其他主要改进
提前编译命令行人机工程学 (Ahead-of-Time Command-Line Ergonomics) [JEP 514]
Project Leyden是Java 25中影响最“出圈”的改进方向,它直接回应了Java长期以来在云原生和Serverless场景下的痛点——启动慢、预热时间长。
JEP 514简化了创建AOT缓存的操作。在JDK 24中,生成AOT缓存需要运行java两次(先record再create)。JDK 25引入了-XX:AOTCacheOutput选项,只需一条命令即可完成记录和缓存创建:
java -XX:AOTCacheOutput=app.aot -cp app.jar com.example.App后续生产运行时,指定缓存文件即可享受加速:
java -XX:AOTCache=app.aot -cp app.jar com.example.App提前编译方法分析 (Ahead-of-Time Method Profiling) [JEP 515]
JEP 515将前一次运行的方法执行分析数据存入AOT缓存。HotSpot JVM启动时即可使用这些数据,JIT编译器无需等待运行时收集profile就能立即生成高效的本地代码,从而“消除典型的预热期”。
紧凑对象头 (Compact Object Headers) [JEP 519]
Java 25将HotSpot JVM中对象头的大小从96到128位减少到64位(64位架构下),这可以减少堆内存占用并提高缓存局部性。该特性在JDK 24中为实验性特性,在JDK 25中转为产品级特性。通过JVM参数-XX:+UseCompactObjectHeaders启用(不再需要-XX:+UnlockExperimentalVMOptions)。
密钥派生函数API (Key Derivation Function API) [JEP 510]
密钥派生函数API提供了标准的密钥派生函数接口,用于从主密钥安全地派生出多个子密钥。该API在JDK 24中作为预览特性引入,在JDK 25中正式定型。它是Java为后量子密码学做准备的关键构件之一。
推荐阅读
- Java 17 新增的特性
- Java 21新增的特性