CodeGym /課程 /JAVA 25 SELF /模組導論:為什麼需要

模組導論:為什麼需要

JAVA 25 SELF
等級 60 , 課堂 0
開放

1. 舊方法的問題

「Classpath party」:大家都擠在同一個廚房

在 Java 引入模組之前(第九版),應用程式的所有程式碼、函式庫與相依都堆在同一大堆 — classpath。函式庫之間幾乎沒有邊界:任何進入 classpath 的類別,都可能被任何人找到並使用。

會出現名稱衝突:兩個函式庫包含相同完整名稱的類別,例如 com.example.Util —— 系統會『挑中某一個』,而你往往在執行期的怪異行為中才發現問題。

經典場景就是 dependency hell:一個函式庫需要記錄器的 1.2 版,另一個需要 1.3,而在 classpath 中兩者同時存在。JVM 不知道該載入哪個,你就會遇到神祕的 NoSuchMethodError

還有:即使類別宣告為 public,但其實只供函式庫內部使用,其他程式碼仍能調用它——並可能在過程中把自己弄壞。大型專案成了雷區,維護也變成了冒險。

2. 什麼是 Java 模組?

模組:像打了孔的盒子

在 Java 9 出現了模組化系統(JPMS)。模組就像裝著類別與套件的盒子,你可以自己打「孔」:明確宣告哪些對外展示(exports),哪些藏在盒子裡。而且是的:就算類別是 public,若其套件未被匯出,其他模組也看不到。

形式化定義

模組是將相關套件與類別聚合在一起的邏輯單位。每個模組會明確宣告:

  • 匯出什麼——讓哪些內容對其他模組可見(exports);
  • 以及它匯入/相依什麼——依賴哪些其他模組(requires)。

模組的關鍵特性:

  • 明確邊界。 清楚定義哪些對外可見,哪些不可見。
  • 明確相依。 沒有顯式的 requires,模組就『看不見』其他模組。
  • 隔離。 內部細節可以完全對外隱藏。

3. 模組化的優點

更好的封裝

出現了新的可見性層級——模組層級。即使類別是 public,但其套件未被匯出,它也僅在模組內可見。函式庫的內部細節不再『滲漏』到外部。

明確的相依描述

所需的相依會列在 module-info.java 中。如果你忘了宣告依賴的模組,編譯器與 IDE 會事先提醒。

更高的安全與可靠性

限制對內部 API 的存取,使得意外或蓄意的干預更困難。這對大型函式庫與平台模組至關重要。

可以建立「精簡版」JRE

透過 jlink 可以只從需要的模組組裝最小的執行期。在雲端與嵌入式情境下,這能節省磁碟空間與記憶體。

額外加分:啟動更快、體積更小

不會載入所有模組,而是只載入實際使用到的——應用程式啟動更快、資源占用更少。

4. 模組在哪裡使用

在 Java 標準函式庫中

自 Java 9 起,整個平台本身就是模組化的。例子:

  • java.base —— 基礎模組(任何程式都必需)。
  • java.sql —— 資料庫操作。
  • java.xml —— XML 處理。

如果你不使用 XML,java.xml 模組甚至不會出現在你的執行期映像中。

在大型應用與函式庫中

對企業系統幾乎是 must-have,特別是有數十個團隊在單一儲存庫(monorepo)中協作時。模組化能降低衝突並簡化維護。

在自己的專案中

即使是個人小專案,模組也有助於鍛鍊架構思維並讓程式碼保持整潔,避免「spaghetti code」。

5. 語法速覽:module-info.java

最重要的是 module-info.java 檔案

module-info.java 檔案位於模組原始碼的根目錄,並宣告其邊界與相依性。

module my.awesome.module {
    exports com.example.api;        // 匯出的套件
    requires java.sql;              // 對標準模組的相依性
}

主要關鍵字:

  • module <名稱> —— 宣告模組。
  • exports <套件> —— 讓該套件對其他模組可見。
  • requires <模組> —— 宣告對其他模組的相依。

最小範例:

module com.myproject.core {
    exports com.myproject.core.api;
}

帶有相依性的範例:

module com.myproject.app {
    requires com.myproject.core;
    requires java.sql;
}

其他功能(簡述):對反射有 opens,對服務則有 usesprovides ... with ...(詳見進階課程)。

6. 實用細節

模組就像類別的「護照」。 以前任何帶著 public 簽證的類別都能在應用程式中「旅行」。現在還需要模組的「護照」——也就是套件匯出(exports)。沒有它,類別就會留在「家裡」。

Dependency hell 是真實存在的術語。 如果你看過 ClassNotFoundException: com.google.common.base.Strings,你就到過那裡。模組的目的就是用嚴格的邊界與相依把這些「惡鬼」趕走。

整個 Java 現在都是模組化的。 即使你不寫自己的模組,平台也已拆成數十個。試試以下指令:

java --list-modules

這對開發有何影響

你能明確管理程式碼的可見性,內部套件不會再因「偶然」而對所有人開放。IDE 與編譯器會更早抓到錯誤:忘了宣告相依——專案就無法建置。大型系統的維護也更簡單:更容易理解誰依賴誰,以及變更會影響到什麼。

7. 遷移到模組時的常見錯誤

錯誤 1:忘了匯出套件,但類別是 public。 你把類別宣告為 public,但沒有匯出它所在的套件——其他模組將無法使用它。編譯器會提示:「套件未由模組匯出」。

錯誤 2:沒有透過 requires 宣告相依。 你使用了另一個模組的型別,但忘了在 module-info.java 中加入 requires。結果——編譯錯誤「模組找不到所需的類別」。

錯誤 3:模組名稱重複。 在大型專案中,兩個模組不小心取得相同名稱。JVM 不會放過這件事——請重新命名並遵守一致的命名規則。

錯誤 4:忘了標準模組。 例如你使用 JDBC,卻沒有加入 requires java.sql;。在 Java 8 中「反正看得到」,但在 9+ 就不是了。

錯誤 5:嘗試使用其他模組的內部類別。 如果套件未被匯出,即使是 public 類別也無法從外部看到。請匯出套件,或把 API 抽到「公開」層。

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION