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.Listjava.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 類別。此類別定義了 printprintlnreadln 三個靜態方法。
利用它可以使用 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 工廠方法獲取實例並進入第五個預覽版本。

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

如下示範,詳細情況稍後會進一步說明!(自 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 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝12   💬6   ❤️5
510
🥈
我愛JS
📝1   💬7   ❤️4
103
🥉
AppleLily
📝1   💬4   ❤️1
58
#4
💬1  
5
#5
xxuan
💬1  
3
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次