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,對服務則有 uses 與 provides ... 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 抽到「公開」層。
GO TO FULL VERSION