JDK 25 重磅發佈了!這是一個非常重要的版本,里程碑式。
JDK 25 是 LTS(長期支持版),至此為止,有 JDK8、JDK11、JDK17、JDK21 和 JDK 25 這五個長期支持版了。
JDK 21 共有 18 個新特性,這篇文章會挑選其中較為重要的一些新特性進行詳細介紹:
- JEP 506: Scoped Values (作用域值)
- JEP 512: Compact Source Files and Instance Main Methods (緊湊源文件與實例主方法)
- JEP 519: Compact Object Headers (緊湊對象頭)
- JEP 521: Generational Shenandoah (分代 Shenandoah GC)
- JEP 507: Primitive Types in Patterns, instanceof, and switch (模式匹配支持基本類型, 第三次預覽)
- JEP 511: Module Import Declarations (模塊導入聲明)
- JEP 513: Flexible Constructor Bodies (靈活的構造函數體)
- JEP 508: Vector API (向量 API, 第十次孵化)
其實裏面的很多新特性在之前的版本中就多次提到了,這裏只是轉正或者再次預覽。
下圖是從 JDK 8 到 JDK 24 每個版本的更新帶來的新特性數量和更新時間:
JEP 506: 作用域值
作用域值(Scoped Values)可以在線程內和線程間共享不可變的數據,優於線程局部變量 ThreadLocal ,尤其是在使用大量虛擬線程時。
final static ScopedValue<...> V = new ScopedValue<>();
// In some method
ScopedValue.where(V, <value>)
.run(() -> { ... V.get() ... call methods ... });
// In a method called directly or indirectly from the lambda expression
... V.get() ...
作用域值通過其“寫入時複製”(copy-on-write)的特性,保證了數據在線程間的隔離與安全,同時性能極高,佔用內存也極低。這個特性將成為未來 Java 併發編程的標準實踐。
JEP 512: 緊湊源文件與實例主方法
該特性第一次預覽是由 JEP 445 (JDK 21 )提出,隨後經過了 JDK 22 、JDK 23 和 JDK 24 的改進和完善,最終在 JDK 25 順利轉正。
這個改進極大地簡化了編寫簡單 Java 程序的步驟,允許將類和主方法寫在同一個沒有頂級 public class的文件中,並允許 main 方法成為一個非靜態的實例方法。
class HelloWorld {
void main() {
System.out.println("Hello, World!");
}
}
進一步簡化:
void main() {
System.out.println("Hello, World!");
}
這是為了降低 Java 的學習門檻和提升編寫小型程序、腳本的效率而邁出的一大步。初學者不再需要理解 public static void main(String[] args) 這一長串複雜的聲明。對於快速原型驗證和腳本編寫,這也使得 Java 成為一個更有吸引力的選擇。
JEP 519: 緊湊對象頭
該特性第一次預覽是由 JEP 450 (JDK 24 )提出,JDK 25 就順利轉正了。
通過優化對象頭的內部結構,在 64 位架構的 HotSpot 虛擬機中,將對象頭大小從原本的 96-128 位(12-16 字節)縮減至 64 位(8 字節),最終實現減少堆內存佔用、提升部署密度、增強數據局部性的效果。
緊湊對象頭並沒有成為 JVM 默認的對象頭佈局方式,需通過顯式配置啓用:
- JDK 24 需通過命令行參數組合啓用:
$ java -XX:+UnlockExperimentalVMOptions -XX:+UseCompactObjectHeaders ...; - JDK 25 之後僅需
-XX:+UseCompactObjectHeaders即可啓用。
JEP 521: 分代 Shenandoah GC
Shenandoah GC 在 JDK12 中成為正式可生產使用的 GC,默認關閉,通過 -XX:+UseShenandoahGC 啓用。
Redhat 主導開發的 Pauseless GC 實現,主要目標是 99.9% 的暫停小於 10ms,暫停與堆大小無關等
傳統的 Shenandoah 對整個堆進行併發標記和整理,雖然暫停時間極短,但在處理年輕代對象時效率不如分代 GC。引入分代後,Shenandoah 可以更頻繁、更高效地回收年輕代中的大量“朝生夕死”的對象,使其在保持極低暫停時間的同時,擁有了更高的吞吐量和更低的 CPU 開銷。
Shenandoah GC 需要通過命令啓用:
- JDK 24 需通過命令行參數組合啓用:
-XX:+UseShenandoahGC -XX:+UnlockExperimentalVMOptions -XX:ShenandoahGCMode=generational - JDK 25 之後僅需
-XX:+UseShenandoahGC -XX:ShenandoahGCMode=generational即可啓用。
JEP 507: 模式匹配支持基本類型 (第三次預覽)
該特性第一次預覽是由 JEP 455 (JDK 23 )提出。
模式匹配可以在 switch 和 instanceof 語句中處理所有的基本數據類型(int, double, boolean 等)
static void test(Object obj) {
if (obj instanceof int i) {
System.out.println("這是一個int類型: " + i);
}
}
這樣就可以像處理對象類型一樣,對基本類型進行更安全、更簡潔的類型匹配和轉換,進一步消除了 Java 中的模板代碼。
JEP 505: 結構化併發(第五次預覽)
JDK 19 引入了結構化併發,一種多線程編程方法,目的是為了通過結構化併發 API 來簡化多線程編程,並不是為了取代java.util.concurrent,目前處於孵化器階段。
結構化併發將不同線程中運行的多個任務視為單個工作單元,從而簡化錯誤處理、提高可靠性並增強可觀察性。也就是説,結構化併發保留了單線程代碼的可讀性、可維護性和可觀察性。
結構化併發的基本 API 是StructuredTaskScope,它支持將任務拆分為多個併發子任務,在它們自己的線程中執行,並且子任務必須在主任務繼續之前完成。
StructuredTaskScope 的基本用法如下:
try (var scope = new StructuredTaskScope<Object>()) {
// 使用fork方法派生線程來執行子任務
Future<Integer> future1 = scope.fork(task1);
Future<String> future2 = scope.fork(task2);
// 等待線程完成
scope.join();
// 結果的處理可能包括處理或重新拋出異常
... process results/exceptions ...
} // close
結構化併發非常適合虛擬線程,虛擬線程是 JDK 實現的輕量級線程。許多虛擬線程共享同一個操作系統線程,從而允許非常多的虛擬線程。
JEP 511: 模塊導入聲明
該特性第一次預覽是由 JEP 476 (JDK 23 )提出,隨後在 JEP 494 (JDK 24)中進行了完善,JDK 25 順利轉正。
模塊導入聲明允許在 Java 代碼中簡潔地導入整個模塊的所有導出包,而無需逐個聲明包的導入。這一特性簡化了模塊化庫的重用,特別是在使用多個模塊時,避免了大量的包導入聲明,使得開發者可以更方便地訪問第三方庫和 Java 基本類。
此特性對初學者和原型開發尤為有用,因為它無需開發者將自己的代碼模塊化,同時保留了對傳統導入方式的兼容性,提升了開發效率和代碼可讀性。
// 導入整個 java.base 模塊,開發者可以直接訪問 List、Map、Stream 等類,而無需每次手動導入相關包
import module java.base;
public class Example {
public static void main(String[] args) {
String[] fruits = { "apple", "berry", "citrus" };
Map<String, String> fruitMap = Stream.of(fruits)
.collect(Collectors.toMap(
s -> s.toUpperCase().substring(0, 1),
Function.identity()));
System.out.println(fruitMap);
}
}
JEP 513: 靈活的構造函數體
該特性第一次預覽是由 JEP 447 (JDK 22)提出,隨後在 JEP 482 (JDK 23)和 JEP 492 (JDK 24)經歷了預覽,JDK 25 順利轉正。
Java 要求在構造函數中,super(...) 或 this(...) 調用必須作為第一條語句出現。這意味着我們無法在調用父類構造函數之前在子類構造函數中直接初始化字段。
靈活的構造函數體解決了這一問題,它允許在構造函數體內,在調用 super(..) 或 this(..) 之前編寫語句,這些語句可以初始化字段,但不能引用正在構造的實例。這樣可以防止在父類構造函數中調用子類方法時,子類的字段未被正確初始化,增強了類構造的可靠性。
這一特性解決了之前 Java 語法限制了構造函數代碼組織的問題,讓開發者能夠更自由、更自然地表達構造函數的行為,例如在構造函數中直接進行參數驗證、準備和共享,而無需依賴輔助方法或構造函數,提高了代碼的可讀性和可維護性。
class Person {
private final String name;
private int age;
public Person(String name, int age) {
if (age < 0) {
throw new IllegalArgumentException("Age cannot be negative.");
}
this.name = name; // 在調用父類構造函數之前初始化字段
this.age = age;
// ... 其他初始化代碼
}
}
class Employee extends Person {
private final int employeeId;
public Employee(String name, int age, int employeeId) {
this.employeeId = employeeId; // 在調用父類構造函數之前初始化字段
super(name, age); // 調用父類構造函數
// ... 其他初始化代碼
}
}
JEP 508: 向量 API(第十次孵化)
向量計算由對向量的一系列操作組成。向量 API 用來表達向量計算,該計算可以在運行時可靠地編譯為支持的 CPU 架構上的最佳向量指令,從而實現優於等效標量計算的性能。
向量 API 的目標是為用户提供簡潔易用且與平台無關的表達範圍廣泛的向量計算。
這是對數組元素的簡單標量計算:
void scalarComputation(float[] a, float[] b, float[] c) {
for (int i = 0; i < a.length; i++) {
c[i] = (a[i] * a[i] + b[i] * b[i]) * -1.0f;
}
}
這是使用 Vector API 進行的等效向量計算:
static final VectorSpecies<Float> SPECIES = FloatVector.SPECIES_PREFERRED;
void vectorComputation(float[] a, float[] b, float[] c) {
int i = 0;
int upperBound = SPECIES.loopBound(a.length);
for (; i < upperBound; i += SPECIES.length()) {
// FloatVector va, vb, vc;
var va = FloatVector.fromArray(SPECIES, a, i);
var vb = FloatVector.fromArray(SPECIES, b, i);
var vc = va.mul(va)
.add(vb.mul(vb))
.neg();
vc.intoArray(c, i);
}
for (; i < a.length; i++) {
c[i] = (a[i] * a[i] + b[i] * b[i]) * -1.0f;
}
}
儘管仍在孵化中,但其第十次迭代足以證明其重要性。它使得 Java 在科學計算、機器學習、大數據處理等性能敏感領域,能夠編寫出接近甚至媲美 C++等本地語言性能的代碼。這是 Java 在高性能計算領域保持競爭力的關鍵。
從 JDK8 到 JDK24 每一版版本的新特性詳細介紹,可以在 JavaGuide 官方網站( javaguide.cn