Java 25 於 2025/9/16 正式發佈。
Java 25 / JDK 25: 一般可用性
Oracle 發佈 Java 25
Java 25 的到來

作為 LTS 版本,Java 25 將有許多來自 Java 21 的變更,因此我認為這將是一個長期使用的版本。這次的主要變化包括 main 方法的簡化 和 模組層級的 import 的正式化。除此之外,看來沒有其他重大影響。
支援 IO.println 因此在補完功能無法使用的環境中編寫程式碼會更方便。
另外, SoundClip 在開發簡單遊戲時非常實用。

如果希望在不安裝 JDK 的情況下嘗試語言或函式庫的新功能,Java Playground 是個不錯的選擇。
https://dev.java/playground/
Samples 中有新功能的範例。
image.png

事件

將在福岡舉辦新功能解說的活動。
10/17(星期五) 在福岡工程師咖啡館將於19點開始進行。
Java 25 發佈活動@福岡 - connpass

資料

詳細資訊請參考
JDK 25 發佈說明
Java SE 25 平台 JSR 400
OpenJDK JDK 25 GA 發佈

API 文件請見
概述 (Java SE 25 & JDK 25)

新增的 API 概覽請見
https://docs.oracle.com/en/java/javase/25/docs/api/new-list.html

API 差異請參見
https://cr.openjdk.org/~iris/se/25/build/latest/java-se--jdk-24-ga--jdk-25%2B36/

發行版

對於 Mac 和 Linux 的安裝,建議使用 SDKMAN!。
除了 Oracle OpenJDK 之外,還有幾個可以無償商業使用的發行版,如下所示:

※ Temurin、Liberica、Microsoft 尚未發佈
如果不知該使用哪個發行版,Eclipse Temurin 是個安全的選擇。
更新的時間表為 10 月將發佈 25.0.1,1 月將發佈 25.0.2。
Oracle JDK 的免費授權 NFTC 將在下一個 LTS 版本之前有效,之後的更新將轉為付費的 OTN,因此需要特別注意。

開發組織

參與開發的組織比例並不高,但日本企業如富士通及 NTT 數據持續貢獻。
Untitled.png

JEP

較大的變更已在 JEP 中匯總。
https://openjdk.org/projects/jdk/25/

本次共納入 18 個 JEP,其中 12 個正式採用。從預覽版本正式化的有 Scoped Values、Module Import Declarations、Compact Source Files 和 Flexible Constructor Bodies 四個,後三者使得程式碼變得更容易撰寫。預覽版本有 6 個,其中 3 個是新納入的。
這次的 JEP 被分為以下 5 個類別:

470: PEM 格式的加密物件編碼 (預覽)
502: 穩定值 (預覽)
503: 移除 32 位 x86 端口
505: 結構化併發 (第五次預覽)
506: Scoped Values
507: Patterns、instanceof 和 switch 中的基本類型 (第三次預覽)
508: 向量 API (第十次孵化)
509: JFR CPU 時間分析 (實驗性)
510: 雜湊功能 API
511: 模組導入聲明
512: 簡潔源文件與實例 main 方法
513: 靈活的建構子主體
514: 提前執行命令行舒適性
515: 提前執行方法分析
518: JFR 協作採樣
519: 簡潔物件標頭
520: JFR 方法計時與追蹤
521: 世代性 Shenandoah

這 18 個 JEP 是自實施半年以來的次多數量,僅次於上次的 24 個。
考量到上次 LTS 的 Java 21 與更早的 Java 17 之間的 JEP 數量為 37 個,這次吸納了從 Java 21 開始的 66 個 JEP,顯示功能有了相當大的改善。不過,同樣內容的 JEP 可能已多次作為預覽引入,需要相應地減少其計數。
Untitled.png

語言功能

在語言功能的變更中,沒有新功能,但之前是預覽的功能已經正式化。
507: Patterns、instanceof 和 switch 中的基本類型 (第三次預覽)
511: 模組導入聲明
512: 簡潔源文件與實例 main 方法
513: 靈活的建構子主體

507: Patterns、instanceof 和 switch 中的基本類型 (第三次預覽)

此功能於 Java 23 中作為預覽引入,並且沒有變更地進入第三次預覽狀態。

instanceof

當使用 instanceof 檢查原始類型時,判斷標準是檢查值是否缺失。

jshell> int i = 128
i ==> 128

jshell> i instanceof byte
$2 ==> false

jshell> i = 127
i ==> 127

jshell> i instanceof byte
$4 ==> true

當用整數類型檢查實數類型時,則會判斷精度是否降低。

jshell> double d = 1
d ==> 1.0

jshell> d instanceof int
$2 ==> true

jshell> d = 1.2
d ==> 1.2

jshell> d instanceof int
$4 ==> false

不過,對於包裝類的物件,當僅是其基本類型時才會返回 true。

jshell> Long num = 123L
num ==> 123

jshell> num instanceof int
|  錯誤:
|  不符合的類型: java.lang.Long無法轉換為int:
|  num instanceof int
|  ^-^

jshell> num instanceof long
$7 ==> true

然後可以用作模式匹配。

jshell> long num = 123
num ==> 123

jshell> var out = new ByteArrayOutputStream()
out ==> 

jshell> if (num instanceof byte b) out.write(b)

jshell> out.toByteArray()
$21 ==> byte[1] { 123 }

jshell> num = 1234
num ==> 1234

jshell> if (num instanceof byte b) out.write(b)

jshell> out.toByteArray()
$24 ==> byte[1] { 123 }

switch

這個模式匹配也可以用於 switch,且由於有常數模式的存在,所以所有類型都可以用於 switch。

jshell> long num = 1234
num ==> 1234

jshell> switch (num) { case int n -> "整數"; default -> "其他";}
$25 ==> "整數"

jshell> num = 30_0000_0000L
num ==> 3000000000

jshell> switch (num) { case int n -> "整數"; default -> "其他";}
$27 ==> "其他"

類似於 if 表達式也可以這麼寫。

boolean flag = isFoo();
var s = switch (flag) {
  case true -> "是的";
  case false -> "不是";
}

511: 模組導入聲明

此功能在 Java 23 中作為預覽引入,並在 Java 25 中正式化。
支援模組層級的 import,進行 java.base 的 import 通常應該會沒問題。此外,外部函式庫的模組支援也應該會跟進。
與 Java 23 時的變更相比,現在依賴的模組會隨之 import。因此導入 java.se 時,會自動導入 Java SE 的所有 API。
像下面這樣會導入屬於 java.base 模組的 java.util 及 java.io。

import module java.base;

在 JShell 中,java.base 會自動被導入。
例如,因為 java.time 並未自動導入,直接使用 LocalDate 會報錯。

>jshell
|  歡迎來到 JShell -- 版本 24
|  如需介紹,請輸入: /help intro

jshell> LocalDate.now()
|  錯誤:
|  找不到符號
|    符號:   變數 LocalDate
|    位置: 類別
|  LocalDate.now()
|  ^-------^

在 Java 25 中,無需導入即可直接使用 LocalDate。

>jshell
|  歡迎來到 JShell -- 版本 25
|  如需介紹,請輸入: /help intro

jshell> LocalDate.now()
$1 ==> 2025-09-16

進行 GUI 程式設計時,需要導入 java.desktop 模組。

jshell> import module java.desktop;

jshell> var f = new JFrame("你好")
f ==> javax.swing.JFrame[frame0,0,0,0x0,invalid,hidden, ... tPaneCheckingEnabled=true]

jshell> var b = new JButton("確定")
b ==> javax.swing.JButton[,0,0,0x0,invalid,alignmentX=0 ... text=確定,defaultCapable=true]

但這樣會導致 List 變成 java.util.List 和 java.awt.List 的重名,因此使用時會發生歧義。

jshell> List a = null;
|  錯誤:
|  List 的引用存在歧義
|    java.awt 的類別 java.awt.List 與 java.util 的介面 java.util.List兩者都符合
|  List a = null;
|  ^--^

在這種情況下,需要導入實際使用的類別,例如 java.util.List。

jshell> import java.util.List

jshell> List a = null
a ==> null

512: 簡潔源文件與實例 main 方法

Java 24 中的 Simple Source Files and Instance Main Methods 改名為 Compact Source Files...並正式化。這樣的命名更為精確。
這讓我們擺脫了 public static void main 的麻煩。
Java 脫離 public static void main 的束縛 - きしだのHatena

在 Java 中,撰寫簡單的 Hello World 程式碼需要以下代碼:

public class Hello {
  public static void main(String[] args) {
    System.out.println("Hello Java!");
  }
}

現在可以這樣編寫:

void main() {
  IO.println("Hello Java!");
}

關鍵字 public、class、static 等都消失了,[] 這種怪異的符號也消失了。
在學習程式中,最初想要做的事是編寫程式碼,而類別是組織這些程式的手段,因此在理解程式之前學習類別並沒有太大意義。
public 等訪問修飾符是為了防止在大型程式中使用不當元素,在入門範例中並不需要。
static 的解釋需要理解類別或實例,但在學習之前來說過早,因此留著這個功能。
陣列等知識也無法在未知變數的情況下提前學習,因此很少在入門時的範例中傳遞參數 args。
這導致「難以理解的習慣」被放置未處理,或只能在無法理解的狀態下進行「System.out.pritln 的解釋」,但因類別名與文件名不同,導致運行的過程中無法完成。

因此,為了集中於初期應學的內容,放寬了以下限制。

  • 不需要定義類別
  • main 方法可以是實例方法
  • 可省略 main 方法的參數
  • main 方法不必是 public

可能會有人問「方法是否也可以不需要?」不過,當前沒有同一層級寫陳述和方法的機制,因此仍需保留,因為推出新的一級函數式樣會大大影響「專注於初期應學的內容」。

相關設計筆記已在以下文檔中概述。
https://openjdk.org/projects/amber/design-notes/on-ramp

main 方法可以是實例方法

main 方法無需使用 static 限定符。因此,因不必將 static 附加於 main 方法,從主方法中調用的其他方法也不需要 static,使撰寫稍大範圍的範例變得容易。

public class Hello {
  public void main() {
    foo();
  }
  void foo() {
    IO.println("Hello");
  }
}

可省略 main 方法的參數 / main 方法不必是 public

不必撰寫無用代碼使得代碼簡潔。
main 方法無法設為 private,protected 則是允許的。
在未看參考的情況下能夠流暢地撰寫「public static void main(String[] args)」既是一種幸福感,失去這部分的確有些遺憾,但這只是一種懷舊想法。

不需要定義類別

除了不需要瞭解類別,還可不必考慮班名,並且縮排減少了,更少出錯的可能,因為只需要一組中括號,做有齊全需找一個合適的中括號。

void main() {
  IO.println("Hello Java");
}

省略類別並使用實例方法的 main 時,將會被包裹在 new Object(){} 中。

new Object() {
  void main() {
    IO.println("Hello Java");
  }
}.main();

不過,在省略類別定義的情況下,如果文件名帶有 static.java 這樣的關鍵字,就會報錯。在有類別定義的情況下,如果直接使用 java 命令執行則不會有問題。

>java static.java
static.java:3: 錯誤: 無效的文件名: static
  void main() {
  ^
錯誤數量 1
錯誤: 編譯失敗

隱含的類別 java.base 將被模組導入

在使用隱式類別時,java.base 模組也會被導入。
該代碼可以無需顯式導入。

void main() {
  var data = List.of("test", "data");
  IO.println(data.stream().collect(Collectors.joining(" ")));
}

513: 靈活的建構子主體

在 Java 22 中「在 super 之前的陳述」改名後於 Java 23 中變更,並在 Java 25 中正式化。
現在可以在呼叫父類別構造函數前使用 this 的語句。
例如,如果有一個接受兩個 List 的類別構造函數:

class Foo {
  Foo(List l1, List l2) {
  }
}

在繼承這個構造函數時,必須給定兩個 List。

class Bar extends Foo {
  Bar() {
    super(List.of("abc", "def"), List.of("abc", "def"));
  }
}

由於都是相同的,想要合併時,由於無法在 super 之前執行語句,需考慮使用迂迴構造。

class Bar extends Foo {
  private Bar(List l) {
    super(l, l);
  }
  Bar() {
    this(List.of("abc", "def"));
  }
}

現在可以這麼寫:

class Bar extends Foo {
  Bar() {
    var param = List.of("abc", "def");
    super(param, param);
  }
}

如此一來,當需要參數檢查、構建或共享的時候,就能以自然的方式進行程式碼的編寫。
若在使用 super 的前面進行字段的訪問,則會導致錯誤。

class Bar extends Foo {
  List<String> param;
  Bar() {
    param = List.of("abc", "def");
    super(param, param);
  }
}
test.java:11: 錯誤: 無法在呼叫超類構造函數之前引用 param
    param = List.of("abc", "def");
    ^

API

API 的變更除安全性外共有 4 個 JEP,其中新增的為 Stable Value。Scoped Value 已正式化。此外,還有許多未成為 JEP 的變更。

502: 穩定值 (預覽)
505: 結構化併發 (第五次預覽)
506: Scoped Values
508: 向量 API (第十次孵化)

小範圍的變更

未成為 JEP 的 API 變更中,有些動作是更容易理解的。

java.lang.IO

引入了 java.lang.IO 類別。此類別定義了 print、println 和 readln 三個靜態方法。
利用它可以使用 IO.println 而非 System.out.println 來編寫 Hello World,如下所示。

void main() {
  IO.println("Hello");
}

若進行靜態導入,僅透過 println 就可進行字符串輸出。

import static java.lang.IO.*;
void main() {
  println("Hello");
}

在 JShell 或記事本等無法使用補全功能的編輯環境中輸入代碼特別方便。

javax.sound.SoundClip

可以這樣播放 WAV 文件。

var file = new File("sound.wav");
var clip = SoundClip.createSoundClip(file);
clip.play();

過去,播放音頻文件最便捷的手段是 Applet API 的 AudioClip,但在 Java 17 中已被標記為過時,預定於 Java 26 中被刪除
因此,引入 SoundClip 類作為便捷播放音頻的替代方案。
[JDK-8356049] 需要一種簡單方法播放聲音片段 - Java 錯誤系統

標準情況下僅支持無壓縮 PCM,因此播放 MP3 時需要引入 SPI。
Java 25 播放 MP3 的方法 - きしだのHatena

在將其用於遊戲音效時的注意事項如下。
Java 25 的 SoundClip 為遊戲添加音效 - きしだのHatena

[Reader.readAllLines](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/io/Reader.html#readAllLines()) / [readAllAsString](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/io/Reader.html#readAllAsString())

在 Reader 類中新增了返回 List<String> 的 [readAllLines](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/io/Reader.html#readAllLines()) 和返回 String 的 [readAllAsString](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/io/Reader.html#readAllAsString()) 這兩個方法。

public List<String> readAllLines() throws IOException
public String readAllAsString() throws IOException

CharSequence.getChars(int, int, char[], int)

在對字符串進行字符級處理時,新增了按範圍將字符批量讀入 char 陣列的方法。
這似乎旨在簡化 Reader.of 的實現,並且在對字符級字符串進行操作時也會提高效率。
[JDK-8343110] 為 CharSequence 和 CharBuffer 新增 getChars(int, int, char[], int) - Java 錯誤系統

BodyHandlers.limiting / BodySubscribers.limiting

在使用 HttpClient 處理傳輸數據的 BodyHandler / BodySubscriber 時,現在可以更輕鬆地設置最大字節數。
如下面這樣執行時,僅獲取 250 字節的字符串。

client.send(request, BodyHandlers.limiting(BodyHandlers.ofString(), 250))

ForkJoinPool 標記代表 ScheduledFuture

在 ForkJoinPool 中新增了與 ScheduledFuture 相關的方法。

  • cancelDelayedTasksOnShutdown()
  • getDelayedTaskCount()
  • schedule(Runnable, long, TimeUnit)
  • schedule(Callable, long, TimeUnit)
  • scheduleAtFixedRate(Runnable, long, long, TimeUnit)
  • scheduleWithFixedDelay(Runnable, long, long, TimeUnit)
  • submitWithTimeout(Callable, long, TimeUnit, Consumer)

StrictMath

每次都有少量方法增加至 StrictMath,本次也新增了 7 個方法。

  • powExact(int, int)
  • powExact(long, int)
  • unsignedMultiplyExact(int, int)
  • unsignedMultiplyExact(long, int)
  • unsignedMultiplyExact(long, long)
  • unsignedPowExact(int, int)
  • unsignedPowExact(long, int)

502: 穩定值 (預覽)

在 Java 中,持有不可變值的機制是 final。

final Logger logger = Logger.create();

不過,當在類別或物件初始化時立即初始化所有不可變值,會導致啟動變慢,或集中存取網路和檔案。
因此,通常希望在需要值時進行初始化。
這是透過訪問器獲取值的方式來實現延遲初始化的。

private Logger logger;
Logger getLogger() {
  if (logger == null) {
    logger = Logger.create();
  }
  return logger;
}

但如此一來,從字段移除 final 會使得 JVM 在優化時的效果減弱。此外考慮到多線程情況,可能需要一些複雜的邏輯。或者,還需要評估在多線程場景下是否允許重複初始化,在沒有實質問題的情況下仍使用不安全的代碼。

此次引入的 StableValue 給了我們一種新的選擇。

private final StableValue<Logger> logger = StableValue.of();
Logger getLogger() {
  return logger.orElseSet(() -> Logger.create());
}

在此時,傳遞給 orElseSet 的初始化代碼僅會被調用一次,由 StableValue 保證,並且是線程安全的。此外,JVM 也會進行優化。

如果覺得定義訪問器太繁瑣,可以使用 supplier。

final Supplier<Logger> logger = StableValue.supplier(() -> Logger.create());

要操作不變值的集合,可以使用 list。

final List<Connection> pool = StableValue.list(10, idx -> new Connection(idx + "th connection"));

如此一來,透過 pool.get(3) 訪問時,對應索引處的值會延遲初始化。
StableValue 為預覽版,使用時需要添加 --enable-preview。

505: 結構化併發 (第五次預覽)

在 Java 19 中作為孵化功能,引入的結構化任務範圍於 Java 24 中從類別變更為介面,並使用 open 工廠方法獲取實例並進入第五個預覽版本。

在並行處理時,執行多個任務時,可能會要求同時完成兩個任務之後正常結束,或其中一個任務完成後退出,也可在其中一個任務出現例外情況時也要結束。
不過,使用現有的 join 和 wait 等方式控制時,實際上容易產生難以追蹤的代碼。
因此,導入結構化並行性。

如下示範,詳細情況稍後會進一步說明!(自 Java 19 起即宣告...)

Response handle() throws InterruptedException {
    try (var scope = StructuredTaskScope.open()) {
        Subtask<String> user = scope.fork(() -> findUser());
        Subtask<Integer> order = scope.fork(() -> fetchOrder());

        scope.join();            // 同時關聯檢測結果是否發生例外

        // 兩個子任務都成功完成后,從而組合結果
        return new Response(user.get(), order.get());
    }
}

Subtask 繼承自 Supplier,因此將其作為 Supplier 使用可能更加合適。

506: Scoped Values

在 Java 20 中作為實驗性引入,Java 21 提供預覽,Java 24 開始正式化,則使用 ScopedValue.orElse 不再接受 null。
共享同一線程內的值時,可以使用 ThreadLocal,並以輕鬆的方式為每個線程指定值。


原文出處:https://qiita.com/nowokay/items/7e05b4c42ded043a298a


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝45   💬2  
666
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
📢 贊助商廣告 · 我要刊登