JDK 24 功能:JEP 494 模組化匯入宣告的更新內容

JEP 494: Module Import Declarations

在 Java 23 中,引入了一項新的語法加強功能預覽:JEP 476 模組化的匯入宣告,讓我們可以更簡潔且高效地一次匯入整個模組函式庫中的類別,而無需使用原有的 single-type-import 和 type-import-on-demand 語法

今年發佈的 Java 24 中,JEP 494 進行第二次的預覽,並伴隨著些微更新以回應社群的使用建議。它預計將於 Java 25 LTS 中成為正式功能,並開放給所有開發人員使用。本文將介紹 JEP 476 與 JEP 494 兩者之間的差異,提供大家做為未來的實作參考。

前言

前一版的 JEP 476(本站介紹)允許開發者透過單一陳述句去匯入模組中的所有公開型別,以簡化模組化函式庫的重複使用。有了這項功能,初學者便能更輕鬆地使用第三方函式庫和基礎 Java 類別,而無需學習它們在套件階層中的位置。它提供了簡潔的方式,將匯入類別與套件的語句簡化成匯入模組,以減少樣板程式碼撰寫:

  • 引入新的 import module 語句,允許一次性匯入模組中的所有型別。例如:import module java.base; 將匯入 java.base 模組中導出的所有套件
  • 新語句可以與現有的 import 語句共存,並且不會影響現有程式碼的行為

模組匯入宣告的語法

JEP 476 的核心是引入了新的模組匯入 import module 語句,這允許開發人員一次匯入模組中導出的所有套件,減少了冗長的匯入語句:

import module java.base;
// 等同於導入 java.base 模組導出的所有套件
// 如 java.util.*, java.io.* 等

import static java.io.IO.*;  // 注意:本類別於 Java 24 中預覽,並於 Java 25 時搬移至 java.lang 套件中
class ModuleImport {
    void main() { 
        var list = List.of("World!", "All!", ":)");
        var n = new Random().nextInt(list.size());
        println("Hello, " + list.get(n));
    }
}

JEP 494 新增功能

JEP 494 引入了下列兩項新的功能:

  • 解除任何模組都不能宣告依賴 java.base 模組的限制,並修改 java.se 模組的宣告可以延伸依賴 java.base 模組。有了這項更新之後,宣告匯入 java.se 模組時將會按需求匯入整個 Java SE API
  • 允許 type-import-on-demand 宣告也可以遮蔽模組匯入宣告(原本只有 single-type-import 可以遮蔽)

解決匯入衝突

當程式中匯入一個或多個模組時,難免會產生不同類別擁有相同名稱的狀況,而導致了匯入衝突。解決的方法是:使用另一個匯入宣告去遮蔽模組匯入宣告。例如,下方的程式碼會造成 Date 匯入衝突。此時使用單一型別宣告(single-type-import)指明實際上所需要使用的型別是哪一個,就可以遮蔽 import module 匯入的 Date 類別並解決模糊匯入:

import module java.base;    // 匯入 java.util,其中有一個公共的 Date 類別
import module java.sql;  // 匯入 java.sql,其中也有一個公共的 Date 類別

import java.sql.Date;       // 解決類別名稱 Date 的不明確性

...
Date d = ...;             // Date 被解析為 java.sql.Date
...

在某些情況下,更方便的方式也許是用隨選型別匯入宣告(type-import-on-demand)去遮蔽整個套件下的所有類別:

import module java.base;
import module java.desktop;
import java.util.*;
import javax.swing.text.*;

...
Element e = ...     // 使用 javax.swing.text.Element 而非 javax.swing.text.html.parser.Element
List l = ...        // 使用 java.util.List 而非 java.awt.List
...

匯入宣告的遮蔽行為與其特異性相關,它們的特異性程度由高到低為:

  • 單一型別匯入
  • 隨選型別匯入
  • 模組匯入

也就是說,單一型別匯入可以遮蔽隨選型別匯入和模組匯入,因為後兩者的特異性較低;而隨選型別匯入可以遮蔽模組匯入,但不能遮蔽單一型別匯入,因為單一型別匯入的特異性更高。

聯合匯入宣告

我們可以聯合數個隨選宣告成單一的模組匯入宣告:

import javax.xml.*;
import javax.xml.parsers.*;
import javax.xml.stream.*;

變成下例更好閱讀的形式:

import module java.xml;

在匯入宣告時分類

當檔案中混合了多種類型的匯入宣告時,將它們分群歸類在一起會更容易閱讀,例如:

// 模組匯入
import module M1;
import module M2;
...

// 隨選型別匯入
import P1.*;
import P2.*;
...

// 單一型別匯入
import P1.C1;
import P2.C2;
...

class Foo { ... }

分群的順序反應了它們的遮蔽能力:最低特異性的模組匯入位在最上方,最高特異性的單一型別匯入位在最下方,中間則是隨選型別匯入。

匯入聚合器模組

聚合器模組本身不直接匯出任何套件,但是會間接匯出它依賴的模組所匯出的套件。例如,java.se 模組不匯出任何套件,但它遞迴地依賴了其他 19 個模組,因此 import module java.se 的效果是匯入那 19 個模組所匯出的套件,也就是 java.se 模組間接地匯出了 123 個套件。

在早期預覽版中,開發人員發現匯入 java.se 模組時並沒有匯入 java.base 模組。解決方法是同時宣告 import module java.base ,或是使用其他額外的匯入宣告,例如 import java.util.*

其原因是 Java 語言規範明確禁止任何模組宣告對 java.base 模組的傳遞依賴關係。在模組功能的原始設計中,此限制是合理的,因為每個模組都隱含地依賴於 java.base。但是,對於使用模組宣告來間接匯入其他模組的功能來說,傳遞地依賴 java.base 的能力是很有用的。

因此,此項限制已被建議取消,並且 java.se 模組會依賴 java.base 模組。我們只需要一個 import module java.se 就可使用所有標準 Java API,無論有多少模組間接地參與匯出。

使用聚合器模組的注意事項

只有 Java 平台中的聚合器模組才應該使用 requires transitive java.base,因為它的目的是為了要匯入所有 java.* 模組,包括 java.base。 Java 平台中同時具有直接和間接匯出的模組嚴格來說並不是聚合器,因此它們不應該使用 requires transitive java.base 而導致污染了命名空間。例如 java.sql 模組匯出其本身和來自 java.xml 的套件,而當我們在某些程式中使用 import module java.sql 時不一定需要從 java.base 匯入內容。

另外,import module java.se 指令目前僅用在少數原始碼檔案中,而這些檔案所屬的模組定義中有明確地指定依賴 java.se。我們無法在模組外的原始碼檔案,或是隱式宣告類別的簡單原始碼檔案中使用 import module java.se。因為它們歸屬於未命名模組,而 java.se 並不存在於未命名模組的預設根模組集之中。

總結

JEP 494 模組匯入宣告的功能不但能夠簡化雜亂的程式碼結構,也能促進更好的模組化設計實踐。對於追求效率和清晰度的開發團隊來說,這無疑是一個值得關注和嘗試的新特性。本功能已進入第二次預覽階段,並計劃於下一個版本中正式推出。

本篇文章的內容為老喬原創、二創或翻譯而來。雖已善盡校對、順稿與查核義務,但人非聖賢,多少仍會有疏漏之處難以避免。如果大家有任何問題、建議或指教,都歡迎在底下留言與老喬討論!

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

11 + fourteen =

目錄
返回頂端