2025年1月18日 星期六

JAVA 7 to 17 Module

一、前言

一、前言

返回目錄

Java 的模組化系統 (Project Jigsaw),正式在 Java 9 中引入,對 Java 的開發方式帶來了重大的改變。在 Java 7 和 8 之前,Java 開發經常會遇到以下問題:

  • 大型應用程式的類別路徑混亂: 龐大的應用程式往往會使用大量的 JAR 檔案,導致類別路徑 (Classpath) 變得難以管理,容易發生版本衝突和命名空間衝突。
  • 封裝性不足: 缺乏明確的介面和實作分離機制,容易讓內部實作細節暴露,降低程式碼的維護性和安全性。
  • 運行時環境臃腫: 即使應用程式只用到 Java SE API 的一部分,仍然需要引入整個 JDK,造成運行時資源的浪費。

模組化系統旨在解決這些問題,透過明確的模組定義,改善程式碼的封裝性、可維護性和效能。以下將整理 Java 7 至 17 間關於模組化的新特性及其相關內容。

目錄

返回目錄



二、模組化概念與架構 (Java 9)

返回目錄

Java 9 引入了模組化系統,核心概念是「模組」(Module)。模組是一個命名過的、自描述的程式碼和資源的集合,它定義了:

  • 模組名稱 (Module Name): 唯一標識模組的名稱。 建議使用套件 (package) 命名慣例,例如 com.example.mymodule
  • 導出 (Exports): 模組向外暴露的 API (程式碼)。
  • 依賴 (Requires): 模組所依賴的其他模組。

模組架構示意圖

返回目錄

graph LR A[Module A] --> B(Module B: Requires A) A --> C(Module C: Requires A) B --> D[Module D: Requires B, C] C --> D E[Module E] F[Module F: Requires E] subgraph Module Container A B C D E F end

此圖展示模組間的依賴關係:

  • Module A: 可以被 B 和 C 模組使用。
  • Module B & C: 都依賴於 Module A。
  • Module D: 依賴於 Module B 和 C。
  • Module E: 是一個獨立的模組。
  • Module F: 依賴於 Module E。
  • Module Container: 代表整個模組系統的容器。

module-info.java 檔案

返回目錄

每個模組都必須包含一個 module-info.java 檔案,用於描述模組的資訊。以下是 module-info.java 中常用的關鍵字及其說明:

  • module : 模組定義檔案以 module 關鍵字開始,後面跟著模組的名稱和定義。
  • requires : 用於指示此模組依賴的其他模組;在此關鍵字後面必須指定模組名稱。
  • requires transitive : requires 關鍵字後指定 transitive ,表示任何依賴此模組的模組都將隱式依賴被 transitive 宣告的模組。
  • exports 及 exports ... to ... : 用於指示模組內哪些套件可以公開訪問;在此關鍵字後必須指定套件名稱。exports…to 指令可讓您精確地指定哪些模組或模組的程式碼可以存取匯出的套裝程式,這稱為 qualified export
  • open, opens 及 opens ... to ... : 在 Java 9 之前,reflection 可用於瞭解套裝程式中的所有類型以及類型的所有成員 (甚至是其 private 成員) ,無論您是否允許此功能。因此,沒有任何東西被真正封裝。在 Java 9 後模組化是個強封裝,只能透過此關鍵字指示模組中哪些套件僅在運行時可以存取,並且可通過反射 API 進行內省;這對於像 Spring 和 Hibernate 這樣高度依賴反射 API 的函式庫非常重要。 opens 也可以在模組層級使用,在這種情況下,整個模組都可以在運行時存取。
  • uses : 用於指示此模組使用的服務介面;在此關鍵字後必須指定型別名稱,即完整的類別/介面名稱。
  • provides ... with ... : 用於指示此模組提供服務介面的實作, with 關鍵字後面的部分表示服務介面的實作。

其基本語法如下:

  • module com.example.mymodule : 聲明模組的名稱為 com.example.mymodule
  • exports com.example.mymodule.publicapi : 導出 com.example.mymodule.publicapi 套件下的類別。
  • requires java.sql : 聲明依賴 java.sql 模組。
  • requires transitive com.example.dependency : 聲明依賴 com.example.dependency 模組,並且該依賴會被傳遞給其他依賴 com.example.mymodule 的模組。
  • opens com.example.mymodule.internal : 聲明 com.example.mymodule.internal 套件可以反射存取。
  • uses com.example.mymodule.MyService : 聲明使用 com.example.mymodule.MyService 服務介面。
  • provides com.example.mymodule.MyService with com.example.mymodule.MyServiceImpl : 聲明提供 com.example.mymodule.MyService 的實作 com.example.mymodule.MyServiceImpl

三、模組化的優點

返回目錄

模組化的主要優點包含:

  • 更強的封裝性: 透過 exports 聲明,可以明確控制哪些 API 可以被其他模組使用,避免內部實作細節洩漏,提升程式碼的安全性。
  • 更明確的依賴管理: 使用 requires 聲明,可以清楚知道模組之間的依賴關係,減少類別路徑衝突,更容易追蹤和管理依賴。
  • 更小的運行時環境: 模組化可以讓 Java 運行時只載入必要的模組,減少資源消耗,提升效能。
  • 更高的可維護性: 模組化程式碼更容易理解和維護,也更容易進行程式碼的重構。
  • Zulu Java 17 有哪些模組呢?使用 java --list-modules 來確認。

四、模組化的工具 (Java 9)

返回目錄

Java 9 提供了相關工具來管理模組,以下列出幾個重要的工具:

  • javac 編譯器,用於編譯模組化的 Java 程式碼,可以根據 module-info.java 檔案來處理模組依賴。
  • java 執行器,用於執行模組化的 Java 程式碼,可以根據模組路徑找到模組和依賴。
  • jlink 連結器,可以將 JDK 中的模組和應用程式的模組組合在一起,生成自包含的運行時映像,减少運行時环境的大小。
  • jmod 模組打包工具,用於建立模組檔案。

範例:編譯和執行模組化程式碼

返回目錄

假設有兩個模組 com.example.modulea com.example.moduleb ,且 com.example.moduleb 依賴 com.example.modulea

  • 這個目錄結構展示了模組化專案的組織方式。
    • bin 目錄用於存放模組化的 JAR 檔案。
    • modules 目錄用於存放編譯後的模組檔案。
    • src 目錄用於存放原始碼。
    • com.example.modulea com.example.moduleb 分別是兩個模組的原始碼目錄。
    • 每個模組的目錄下都有 module-info.java 檔案,以及各自的程式碼檔案。

com/example/modulea/module-info.java

  • 這個 module-info.java 檔案定義了 com.example.modulea 模組。
    • module com.example.modulea :聲明了模組的名稱。
    • exports com.example.modulea.publicapi :導出了 com.example.modulea.publicapi 這個套件,表示其他模組可以訪問此套件下的類別。
    • 重點: 沒有導出 com.example.modulea.internal 套件,這意味著 com.example.modulea 模組內部的程式碼不會被其他模組直接訪問。

com/example/modulea/publicapi/PublicClass.java

  • 這個 Java 類別 PublicClass 位於 com.example.modulea.publicapi 套件下,它有一個 sayHello 方法,會印出 "Hello from Module A!"。
    • 這個類別是 com.example.modulea 模組公開的 API。

com/example/modulea/internal/InternalClass.java

  • 這個 Java 類別 InternalClass 位於 com.example.modulea.internal 套件下,它有一個 saySecret 方法,會印出 "This is a secret from Module A!"
    • 這個類別是 com.example.modulea 模組內部的實作,沒有被導出。

com/example/moduleb/module-info.java

  • 這個 module-info.java 檔案定義了 com.example.moduleb 模組。
    • module com.example.moduleb :聲明了模組的名稱。
    • requires com.example.modulea :表示 com.example.moduleb 模組依賴於 com.example.modulea 模組。

com/example/moduleb/Main.java

  • 這個 Java 類別 Main 位於 com.example.moduleb 套件下,是 com.example.moduleb 模組的主要程式進入點。
    • 它引入了 com.example.modulea.publicapi.PublicClass ,並建立實例並調用 sayHello() 方法,展示了如何使用公開的模組 API。
    • 注意: com.example.modulea.internal.InternalClass 被註解,因為 com.example.modulea 模組並未導出 com.example.modulea.internal 套件,所以無法直接使用。

編譯 com.example.modulea 成功:

  • 這個命令使用 javac 編譯器編譯 com.example.modulea 模組。
    • -encoding utf-8 :設定原始碼的編碼為 UTF-8,避免中文等特殊字元出現問題。
    • -d modules :指定編譯後的輸出目錄為 modules ,編譯後的模組相關 .class 檔案會輸出到 modules 目錄底下,並且會自動以模組名稱建立子資料夾。
    • --module com.example.modulea :指定要編譯的模組名稱。
    • --module-source-path src :指定模組原始碼的目錄為 src javac 會在此目錄下尋找模組的程式碼。
    • 執行後結果: 會在 modules 目錄下建立 com.example.modulea 目錄,並將編譯後的檔案 ( .class 檔案以及 module-info.class ) 放入其中。

編譯 com.example.moduleb 失敗:

錯誤:找不到 com.example.modulea.internal 套件,因為com.example.modulea模組沒有 exports 開放給其他模組使用 com.example.modulea.internal 套件。

  • 這個命令嘗試編譯 com.example.moduleb 模組,使用了與編譯 com.example.modulea 相似的選項。
    • 但是由於 com.example.moduleb/Main.java 中嘗試引入和使用 com.example.modulea.internal.InternalClass ,而 com.example.modulea 模組並未導出該套件,因此編譯器會報錯,指出找不到 com.example.modulea.internal 套件。
    • 重點: 這個錯誤示範了模組化的封裝性,未導出的套件無法被其他模組使用。

調整修改 com/example/moduleb/Main.java ,將未 exports 套件註解,再次編譯

  • 修改了 Main.java 檔案,將 com.example.modulea.internal.InternalClass 的引入和使用程式碼註解起來,使其不再嘗試使用未導出的套件。

編譯 com.example.moduleb 成功:

  • 這個命令再次編譯 com.example.moduleb 模組。
    • 由於 Main.java 中不再使用未導出的套件,編譯成功。
    • 執行後結果: 會在 modules 目錄下建立 com.example.moduleb 目錄,並將編譯後的檔案放入其中。

執行:

輸出結果:

  • 這個命令使用 java 執行器執行 com.example.moduleb 模組的主要類別。
    • -p "modules/" :指定模組路徑,告訴 java 執行器從 modules 目錄中尋找模組。
    • -m "com.example.moduleb/com.example.moduleb.Main" :指定要執行的模組和主類別,格式為 模組名/主類別的完整類別名
    • 執行後結果: com.example.moduleb.Main 類別會被執行,它會調用 com.example.modulea.publicapi.PublicClass sayHello() 方法,輸出 Hello from Module A!

執行完成後的目錄結構:

五、將模組打包成 JAR 檔案

返回目錄

除了直接執行模組化的程式碼,也可以將模組打包成 JAR 檔案,方便部署和管理。以下示範如何將 com.example.modulea com.example.moduleb 模組打包成 JAR 檔案,並使用 JAR 檔案來執行程式。

  1. 打包 com.example.modulea 模組:

    • 這個命令使用 jar 工具將 com.example.modulea 模組打包成 JAR 檔案。
      • --create :表示建立新的 JAR 檔案。
      • --file=bin/com.example.modulea.jar :指定 JAR 檔案的輸出路徑和名稱為 bin/com.example.modulea.jar
      • --module-version=1.0 :設定模組的版本號為 1.0。
      • -C modules/com.example.modulea/ . :表示從 modules/com.example.modulea/ 這個目錄讀取所有內容,包括編譯後的 .class 文件和 module-info.class 檔案。
    • 執行後結果: 會在當前目錄下建立 bin 資料夾,並在其中生成 com.example.modulea.jar 檔案。
  2. 打包 com.example.moduleb 模組:

    • 這個命令與打包 com.example.modulea 模組類似,只是打包的是 com.example.moduleb 模組,並將輸出路徑設定為 bin/com.example.moduleb.jar
    • 執行後結果: 會在當前目錄下建立 bin 資料夾,並在其中生成 com.example.moduleb.jar 檔案。
  3. 使用 JAR 檔案執行程式:

    輸出結果:

    • 這個命令使用 java 執行器執行 com.example.moduleb 模組的主要類別,但這次是使用 JAR 檔案的方式。

      • -p "bin/com.example.modulea.jar;bin/com.example.moduleb.jar" :指定模組路徑,告訴 java 執行器從 bin 目錄中的 JAR 檔案中尋找模組,多個 JAR 檔案之間用分號 ; 分隔。
      • -m "com.example.moduleb/com.example.moduleb.Main" :指定要執行的模組和主類別。
      • 執行後結果: com.example.moduleb.Main 類別會被執行,它會調用 com.example.modulea.publicapi.PublicClass sayHello() 方法,輸出 Hello from Module A!
    • 使用 -p 選項指定 JAR 檔案的路徑,而不是目錄。

    • 其他執行方式不變。

執行完成後的目錄結構:

六、Module 工具:jmod 與 jlink

返回目錄

(6.1) jmod:模組封裝工具

返回目錄

JDK 9 引入了一種稱為JMOD的新格式來封裝模塊。 jmod 主要用於 建立和檢查模組的描述檔 ,以及 打包模組的各種資源 。簡單來說, jmod 讓你將模組的程式碼、資源、原生程式庫、文件等整理成一個單一的 .jmod 檔案,方便管理和分發,可以在JDK_HOME/jmods目錄中找到它們。

  • jmod 的作用 javase-17-jmod

    • 建立模組描述檔: .jmod 檔案內包含 module-info.class ,定義了模組的名稱、相依性、公開的套件等資訊。
    • 打包資源: 可以將類別檔案、資源檔、原生程式庫、以及其他相關檔案打包進 .jmod 檔案。
    • 管理版本: 可以儲存模組版本資訊。
    • 文件管理: 可以打包模組的 API 文件。
  • jmod 的基本用法:

    • jmod create : 建立新的 .jmod 檔案。
    • jmod list : 列出 .jmod 檔案內的內容。
    • jmod describe : 顯示 .jmod 檔案的模組描述資訊。
    • --class-path : 指定尋找模組class的路徑或模組jar檔案。
    • <jmod-file> : 指定 .jmod 檔案的路徑。
  • jmod 範例:

    我們將 com.example.modulea 和 com.example.moduleb 打包成 jmod:

  • 查看jmod內容:

  • 查看jmod模組描述:

    此範例會將編譯後的 .class 檔案與 module-info.class 一起打包進 jmods/com.example.modulea.jmod jmods/com.example.moduleb.jmod

(6.2) jlink:連結器

返回目錄

jlink 主要用於 建立自訂的、最小化的 JRE (Java Runtime Environment) 。它可以根據你的應用程式所需要的模組,建立一個只包含必要模組的 JRE。這有助於減少應用程式的部署大小,以及提高執行效率。

  • jlink 的作用:

    • 客製化 JRE: 可以根據專案的模組需求,建立客製化的 JRE。
    • 減少部署大小: 只包含應用程式所需的模組,減少 JRE 的體積。
    • 提高執行效率: 減少 JVM 加載時間和記憶體佔用。
  • jlink 的基本用法:

    • --module-path : 指定尋找模組的路徑。
    • --add-modules : 指定要包含在 JRE 中的模組。
    • --output : 指定輸出 JRE 的目錄。
  • jlink 範例:

    1. 假設我們已經有一個模組的 jar檔 mymodule.jar (你可以用上面的範例,然後把 out 目錄下的 com 目錄打包成 jar檔)。

    2. 建立自訂 JRE:

      • $JAVA_HOME 必須是你電腦上 JDK 的安裝目錄。
      • ./jmods com.example.modulea.jmod com.example.moduleb.jmod 的所在目錄。
      • 這裡我們指定 java.base (Java 核心模組)以及我們自訂的 com.example.modulea com.example.moduleb 模組。
    3. 執行應用程式:

      執行結果

    此範例會建立一個名為 myjre 的目錄,裡面包含一個只包含 java.base com.example.modulea com.example.moduleb 的 JRE。

    要分發我們自己的Java應用程序,只需要將這個jre目錄打個包給對方發過去,對方直接運行上述命令即可,既不用下載安裝JDK,也不用知道如何配置模組,非常方便分發和部署。

(6.3) 工具簡述:

返回目錄

  • jmod 用於封裝和管理模組檔案,類似於將模組打包成一個容器,便於儲存和分發。
  • jlink 用於建立自訂的、精簡的 JRE,以便應用程式部署時,只需要必要的模組,減少體積。

執行完成後的目錄結構:

七、Java Module Info 中的關鍵字

返回目錄

在 Java 9 引入模組化系統 (Java Platform Module System, JPMS) 後,模組之間的封裝性是核心概念之一。模組的 module-info.java 檔案中,除了使用 exports requires 之外,還可以其他更進階的關鍵字用法,以下我們來一一介紹。

(7.1) opens

返回目錄

  • 允許反射存取: opens 關鍵字允許其他模組,透過反射機制 (Reflection API) 來存取被指定的套件內的 所有類型 (包括公開、預設、保護、私有),以及它們的所有成員 (包括公開、預設、保護、私有)。

  • 鬆綁封裝: exports 僅允許其他模組存取公開類別和成員不同, opens 打開了更廣泛的存取權限,允許其他模組繞過模組的正常封裝限制。

  • 特殊情境使用: opens 通常用於:

    • 框架/工具開發: 像是 Jackson、Gson 等 JSON 處理庫,或 Hibernate 等 ORM 框架,需要利用反射來操作對象,讀取私有欄位和方法。
    • 測試: 測試框架可能需要反射來存取應用程式的私有部分,以便進行更深入的測試。
    • 動態代理: 創建動態代理的程式碼,也可能需要反射存取目標物件的私有成員。
  • opens 的語法

    • <module-name> : 您的模組名稱。
    • <package-name> : 您要開放反射存取的套件名稱。
    • to <module-name1>, <module-name2> (可選): 將反射存取權限限制於特定模組。若省略 to 子句,則代表開放給所有模組。
  • 範例: 假設我們有一個名為 com.example.mymodule 的模組,其中有一個 com.example.mymodule.internal 套件,裡面有一些內部使用的類別。

    • 開放給所有模組:

      此範例中, com.example.mymodule.internal 中的所有類別和成員,都可以被其他模組透過反射存取。

    • 開放給特定模組:

      此範例中,只有 com.example.othermodule 可以反射存取 com.example.mymodule.internal 中的類別和成員。其他模組則無法進行反射存取。

  • 注意事項

    • 謹慎使用 opens : 由於 opens 會繞過模組的封裝機制,因此應該謹慎使用,只在必要的狀況下才使用。濫用 opens 會破壞模組化的好處,降低程式碼的可維護性與安全性。

    • opens exports 的差異:

      • exports : 允許其他模組 正常存取 (編譯時和執行時) 指定套件中的 公開類別和成員
      • opens : 允許其他模組透過 反射 存取指定套件中的 所有類型和成員
    • 反射風險: 儘管反射提供靈活性,但使用反射會增加程式碼的複雜度和維護難度,並可能造成執行時錯誤。

(7.2) uses

返回目錄

uses 關鍵字用於聲明一個模組 依賴 某個 服務介面 (service interface) ,但 不直接依賴 實作該介面的特定模組。它揭示了模組使用服務的意圖,但將服務的實際提供者 (implementation) 的選擇延遲到執行時。

  • uses 的用途

    • 服務發現 (Service Discovery): uses 配合 provides...with... 關鍵字,是 Java 模組系統中實現服務發現機制的基礎。
    • 解耦 (Decoupling): uses 讓模組可以依賴介面,而不直接依賴於提供該介面的具體實現,達到解耦的效果。這使得更換或擴充實作方式更加容易,而不需要修改依賴的模組。
    • 延遲綁定 (Late Binding): uses 並不會在編譯時確定哪個模組提供了服務,而是在執行時由 Java 模組系統根據模組圖的配置來確定。
    • 可插拔 (Pluggable) 系統: 透過 uses provides ,我們可以建立可插拔的系統,其中不同的模組可以提供不同的服務實現,而應用程式可以在執行時動態地選擇它們。
  • uses 的語法

    • <module-name> : 您的模組名稱。
    • <service-interface-name> : 您要依賴的服務介面的完整名稱 (fully qualified name)。這個介面通常會被宣告為 public
  • 範例

    假設我們有以下三個模組:

    • com.example.myserviceapi : 定義服務介面的模組

    • com.example.myclient : 使用服務的模組 (消費者)

    • com.example.myserviceimpl : 提供服務的模組 (提供者)

    • 說明

      • com.example.myclient 模組使用 uses 宣告它依賴 com.example.myserviceapi.MyService 介面。
      • com.example.myserviceimpl 模組使用 provides...with... 宣告它提供了 com.example.myserviceapi.MyService 的實作,並且實作類別為 com.example.myserviceimpl.MyServiceImpl
      • 在執行時, ServiceLoader 會根據 module-info.java uses provides 聲明來找出可用的 MyService 實作並注入。
  • 注意事項

    • uses 關鍵字本身不執行任何注入行為,它只是聲明對服務的依賴。實際的服務加載由 java.util.ServiceLoader 或其他相關的服務加載機制完成。
    • 一個模組可以 uses 多個服務介面。
    • 一個服務介面可以被多個模組 uses
    • uses 的使用,通常會搭配 provides...with... 一起使用,以實現服務發現和解耦。

(7.3) provides...with...

返回目錄

provides...with... 關鍵字用於聲明一個模組 提供 某個 服務介面 (service interface) 具體實作 。它與 uses 關鍵字搭配使用,共同構成了 Java 模組系統中服務發現機制的基礎。

  • provides...with... 的用途

    • 服務提供 (Service Provision): provides...with... 聲明了某個模組提供了特定服務介面的實作。
    • 服務發現 (Service Discovery): 結合 uses provides...with... 讓模組系統在執行時可以找到滿足服務依賴的實作模組。
    • 實現解耦 (Decoupling): 透過 provides...with... ,模組可以提供服務的實作,而不需要直接依賴於使用該服務的模組,達到解耦的效果。
    • 可插拔系統 (Pluggable Systems): provides...with... 允許不同的模組提供同一服務介面的不同實作,使得系統可以根據需求選擇合適的實作,增加了系統的靈活性。
    • 延遲綁定 (Late Binding): provides...with... 不會在編譯時綁定具體實作,而是延遲到執行時由模組系統來解析。
  • provides...with... 的語法

    • <module-name> : 您的模組名稱。
    • <service-interface-name> : 您要提供的服務介面的完整名稱 (fully qualified name)。這個介面通常會被宣告為 public
    • <implementation-class-name> : 提供服務介面實作的類別的完整名稱 (fully qualified name)。這個類別必須實作 <service-interface-name> 介面,且通常會被宣告為 public
  • **範例:**假設我們有以下三個模組:

    • com.example.myserviceapi : 定義服務介面的模組

    • com.example.myclient : 使用服務的模組 (消費者)

    • com.example.myserviceimpl : 提供服務的模組 (提供者)

    • 說明

      • com.example.myserviceimpl 模組使用 provides com.example.myserviceapi.MyService with com.example.myserviceimpl.MyServiceImpl; 宣告它提供了 com.example.myserviceapi.MyService 介面的實作,並且具體實作類別為 com.example.myserviceimpl.MyServiceImpl
      • com.example.myclient 模組使用 uses com.example.myserviceapi.MyService; 宣告它依賴 com.example.myserviceapi.MyService 介面。
      • 在執行時, ServiceLoader 會根據 module-info.java uses provides...with... 聲明,找到 MyService 的實作,並將其實例返回給客戶端模組。
  • 注意事項

    • 一個模組可以提供多個服務介面的實作,只需要使用多個 provides...with... 語句。
    • 一個服務介面可以被多個模組實作,Java 模組系統會根據模組圖的配置選擇合適的實作。
    • provides...with... 通常會與 uses 關鍵字一起使用。

八、Java 9 之後的模組化演進 (Java 10 - 17)

返回目錄

Java 9 引入模組化系統之後,後續的版本主要針對模組化做了一些細微的改進和優化,沒有引入革命性的改變。主要集中在以下幾點:

  • 模組化的微調: 例如改進模組的依賴管理、導出的機制。
  • 與其他新特性的整合: 確保模組化系統與 Java 的其他新特性兼容。
  • 效能優化: 針對模組化的運行時效能做優化。

值得注意的是,Java 17 中,模組化系統已經趨於穩定,成為 Java 應用開發的重要組成部分。

九、總結

返回目錄

Java 模組化 (Module) 是 Java 開發中一個重要的里程碑,解決了傳統 Java 開發中許多問題,帶來了更強的封裝性、更明確的依賴管理、更小的運行時環境,和更高的可維護性。雖然 Java 9 是模組化的關鍵版本,之後的版本主要在細節上進行優化和調整,但模組化的重要性不容忽視。對於現代 Java 開發,模組化是基礎且重要的知識,可以讓開發者建構更穩健、高效且可維護的應用程式。理解和掌握模組化的概念和使用,將對 Java 開發者有莫大的幫助。

返回目錄

沒有留言:

張貼留言