在 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 模組匯入宣告的功能不但能夠簡化雜亂的程式碼結構,也能促進更好的模組化設計實踐。對於追求效率和清晰度的開發團隊來說,這無疑是一個值得關注和嘗試的新特性。本功能已進入第二次預覽階段,並計劃於下一個版本中正式推出。
本篇文章的內容為老喬原創、二創或翻譯而來。雖已善盡校對、順稿與查核義務,但人非聖賢,多少仍會有疏漏之處難以避免。如果大家有任何問題、建議或指教,都歡迎在底下留言與老喬討論!


發佈:
更新:
瀏覽:
分類:
標籤:



