--- title: 'JDK 管理 Java 應用、JavaDoc、Java cmd' disqus: kyleAlien --- JDK 管理 Java 應用、JavaDoc、Java cmd === ## Overview of Content 如有引用參考請詳註出處,感謝 :smile_cat: :::success * 如果喜歡讀更好看一點的網頁版本,可以到我新做的網站 [**DevTech Ascendancy Hub**](https://devtechascendancy.com/) 本篇文章對應的是 [**深入了解 Java 應用與編譯:從原始檔到命令產出 JavaDoc 文件 | JDK 結構**](https://devtechascendancy.com/java-compilation_jdk_javadoc_jar_guide/) ::: [TOC] ## Java 應用 - 概述 Java 應用由多附檔名為 `.java` 的檔案構成(Java 原始檔),以編譯的角度來看則是待編譯的 **編譯單元 (`Compilation Unit`)** ```java= public class HelloJava { } ``` ### Java 原始檔 - 結構 * Java 原始檔內可以包含以下內容,這些內容有部份有一些限制 | 內容關鍵字 | 說明 | 限制 | | - | - | - | | `package` | 套件宣告敘述,**描述該類在 Java 套建中的位置** | **非必須**,但如果存在,那 **只能有一個**、必須 **在檔案最頂端**(除了註解之外) | | `import` | 套件引用 | 0 ~ 多個 | | `class` | 類 | 0 ~ 多個,下面會多做說明 | | `interface` | 界面 | 0 ~ 多個 | :::success * JDK 有提供一些基本 Java 套件,我們可以用 `package` 將其做一些簡單的分類;JDK 的所有套件類也被稱為 **J2SE API** | JDK 套件名 | 功能概述 | | - | - | | `java.lang` | 包含 `Thread`、`Exception`、`System`、`Integer`、`String`... 等等類(**它是 JVM 自動引用的套件**) | | `java.awt`、`java.swing` | 抽象工具箱套件,可用來建立 GUI 畫面 | | `java.io` | Input/Output 套件 | | `java.util` | 工具類別,包含 `Date`、`Collection`... 等等 | | `java.net` | 支援 TCP/IP 網路協定,包含 Socket、URL 相關的類別 | | `javax.*` | 對基本套件的擴充 (聲音程式 `javax.sound`) | ::: * **`package` 套件說明**: 套件 `package` 非必須,而如果有,那 **只能有一個**、必須 **在檔案最頂端**(除了註解之外;其路徑使用 `.` 取代 `/` 符號 而 `package` 描述是相對於 Java 包的路徑 (路徑為 `src/main/java` 或是 `src/main/kotlin` 之下) > 假設我的原始擋在專案中的路徑為 `/src/main/java/hello/FirstJava.java`,那 `package` 則是描述為 `package hello;` * 而套件的作用如下 1. 透過 `package` 對路徑的描述,能區分名稱相同的類名 > 同類名用在同一個檔案中,就必須使用 **全路徑呼叫類** 編譯器才不會搞錯 2. 受到存取許可全的約束(Java 有檔案區域許可全的約束) 3. 有助於劃分、組織 Java 應用的是的類別,將相關類匯聚且分類 * **`class` 類說明**:`class` 關鍵字用來聲明該檔案中有哪些類,而 **Java 檔名、public 類 關係** 有兩種狀況,如下 1. 一個 Java 檔中,可以不必有 public 類 ```java= class HelloJava { } class WorldJava { } ``` 2. 如果有 public 描述的類,**那 Java 原始檔檔名也必須與這個 public 描述的類相同** ```java= // 檔名必須為 HelloJava.java public class HelloJava { } class WorldJava { } ``` * **`import` 類說明**: `import` 關鍵字用來引入不同 `package` 路徑的類,並且 **依照寫法會引入不同的類** 1. 不同的引入方式 ```java= // 只引入 ~/hello/world/FirstImport 類 import hello.world.FirstImport; // 引入 ~/hello/world/ 包下的所有類 import hello.world.*; // 引入 ~/hello/ 包下的所有類 import hello.*; ``` 2. 要注意的是 `*` 號只會引入該目錄下的所有類,並不包括子資料夾的類 ```java= // 只引入 ~/hello/world/FirstImport 類 import hello.world.FirstImport; // 引入 ~/hello/ 包下的所有類,**不包括 world 資料夾內的類!** import hello.*; ``` :::success * **對編譯速度的影響**? import` 用來告訴編譯器該檔案存在套件中的哪裡,如果你能 **清晰的指出套件位置,那就可以加快編譯器搜尋檔案的速度** :::: :::warning * **引入就代表了初始化**? Nonono~ 使用 `import` 時,**不會導致類別的初始化**(不會觸發 ClassLoader 加載類) ::: ### Java 程式進入點 - main * `main` 方法是 Java 應用程式的進入點,**它必須存在於某個類中**(包含程式進入點 `main` 的類,稱之為主程式類別(`mainclass`);`main` 方法須符合以下四個條件 1. 存取限制 `public` 2. 靜態方法 `static` 3. 參數限制 `String[] args` 4. 返回 `void` ```java= // 主程式類別為 HelloJava public class HelloJava { // 必須四個條件都符合 public static void main(String[] args) { } } ``` :::info * 怎樣對 `main` 函數傳入參數? 首先先使用 `javac` 編譯源碼,再使用 `java` 命令執行以下格式就可以對 main 函數傳入參數 ```shell= javac <javasource> java [options] <mainclass> [args...] ``` ::: ## JDK 管理應用 管理 Java 應用程式是指 **建立 Java 應用程式的目錄結構、編譯、執行、發布 Java 應用程式的操作**;下表為 Java 應用常用的開發目錄 | 目錄 | 說明 | | - | - | | `src` | 存放 Java 原始檔 | | `classes` | 編譯過後的 Java 類別檔 (**路徑與 `src` 原始檔批配**) | | `lib` | 其他 `.jar` 包 | | `doc`, `dec/api` | 存放 JavaDoc 相關文件 | | `deploy` | 存放 Java 應用程式的包裹檔案(Jar 文件) | ### JDK 結構簡介 * JDK `Java Development Kit` 它提供 Java 應用程式基礎的開發、執行環境;其中包括以下重點組件 1. **Java 虛擬機**:負責 **解析**、**執行** Java 程式,它代替我們與各平台差異的機器溝通(因此 Java 應用不受限於平台限制) 2. **JDK 庫**:如先前介紹,提供開發者基礎的各個套件使用 3. **開發工具**:開發工具都是些可執行程式,如下 | JDK 提供的開發工具 | 功能 | | - | - | | `javac` | 編譯工具 | | `java` | 執行工具 | | `javadoc` | 生成 JavaDoc 文件的工具 | | `jar` | 打包、解開 Java 包的工具 | > ![](https://hackmd.io/_uploads/rkrQaTZa2.png) ### javac 編譯原文件 * javac 命令用於編譯 Java 原始檔,命令格式如下 ```shell= javac [options] [sourcefiles] ``` 常見的 option 如下 | javac option | 說明 | | - | - | | `-nowarn` | 不輸出警告資訊 | | `-verbose` | 輸出編譯執行中的詳細資訊 | | `-deprecation` | 輸出程式中 **Deprecated API** 的具體位置 | | `-classpath <路徑>` | 覆蓋 `classpath` 環境變數,**用來指定編譯時搜尋相關檔案的路徑(常用在第三方 jar 包)**,如果沒指定則預設當前目錄 | | `-sourcepath <路徑>` | 指定 Java 原始檔的路徑 | | `-d <目錄>` | 編譯後預計輸出 class 到哪個目標目錄 | :::warning * `-classpath` 指定的是編譯時尋找可用 `.class` 的目錄; 如果你要編譯的檔案他所需要的 `.class` 檔案在同層目錄,那你應該指定它的上層目錄,而不是上層目錄 1. **源碼目錄**:`apple.java` 依賴 `banana.java` ```shell= # 目錄 src test/ |-- apple.java |-- banana.java ``` 2. **分開來編譯**: * 先編譯 `apple.java`,下達以下命令會產生 `apple.class` ```shell= # 切換到目錄 cd test/ javac apple.java ``` * 再編譯 `banana.java`,下達以下命令會產生 `banana.class` ```shell= # 由於你已經在源碼目錄內,所以必須指定其上層目錄! # 因為 javac 會尋找的是目錄,假設你指定當前目錄 . 或是 `pwd` # 那它會找不到 class 檔案 javac -classpath .. banana.java ``` ::: **範例**:當前目錄如下 > ![](https://hackmd.io/_uploads/ryZsF5rY3.png) 1. CLI 切換到 `Main.java` 目錄之下並執行以下命令 ```shell= javac -verbose \ -sourcepath `pwd` \ -classpath ../classes \ -d ../classes \ Main.java ``` :::info * 編譯器先到 `-classpath` 指定的目錄下尋找是否有 `Main.class` 檔案,如果沒有再到 `-sourcepath` 指定的目錄下尋找 `Main.java` 檔 * 如果同時找到 `Main.class`、`Main.java` 檔案,那就會比較 class 檔案是否過期,如果過期就重新編譯 java 檔 * 如果只找到 `Main.class` 就直接用 * 如果只找到 `Main.java` 就將其編譯程 class 檔 * 都找不到就拋出錯誤! ::: > ![](https://hackmd.io/_uploads/B1mTtqBKn.png) 2. javac 就會將 Java 原始檔編譯,並放置 `classes` 目錄之下 > ![](https://hackmd.io/_uploads/rJL3KcSth.png) ### java 執行應用 * java 命令用於執行編譯過後的 class,命令格式如下 ```shell= java [options] <classfiles> ``` 常見的 option 如下 | java option | 說明 | | - | - | | `-verbose` | 輸出執行中的詳細資訊 | | `-classpath <路徑>` | 覆蓋 `classpath` 環境變數,**用來指定編譯時搜尋相關檔案的路徑(常用在第三方 jar 包)**,如果沒指定則預設當前目錄 | | `-D<屬性=數性值>` | 這個數性值會定應到 System API,`System.getProperty("屬性")` 中 | | `-jar` | 執行 Jar 檔案中的特定 Java 類別(**必須給予完整路徑**) | **範例**: 1. **沒有包(Package)的類**: 停留在 `src/` 目錄下,並執行以下命令就可以執行 class 檔 ```shell= java -classpath ../classes/ Main ``` > 如果有多個 classpath 可以使用 `;` 符號分離不同路徑 > > eg. `java -classpath ../classes/; ../classesA/; ../classes/B; Main` > ![](https://hackmd.io/_uploads/Hkgcp9SK2.png) :::info * **尋找 classpath** classpath 會從最近的設定開始尋找,尋找的優先級如:命令設定的 `-classpath` > shell 設定的 `classpath` > 系統設定的 `classpath` * **`package` 設定** 運行時 java 會判斷 class 檔案內的 `package` 值,並到對應資料夾中找 `class` 檔案,如果找不到則會拋出 ::: 2. **有包(Package)的類**: 呼叫某個包內 Class 內部的 Main,呼叫的格式 `<包名*>.<類名>` ```java= package samplePackage; public class Apple { public static void main(String[] args) { System.out.println("Hello Apple"); } } ``` 編譯、執行指令如下 ```shell= javac src/samplePackage/Apple.java # 指定到 src 目錄下找 class # 找 samplePackage 包下的 Apple java -classpath ./src samplePackage.Apple ``` :::info * 而 `-classpath` 的設定則要依照你要呼叫的類的 `package` & 你當前的路徑去做設定 > 當前路徑是 `/Users/Hy-KylePan/IdeaProjects/JavaTest` > > Class 檔案是在當前路徑下的 `src/samplePackage/Apple.class` > > 而 Apple.class 的 `package` 是 samplePackage,相較之下是缺少了 `./src` 目錄,所以 `-classpath` 要設定 `./src` ::: > ![](https://hackmd.io/_uploads/BkQrIl7T3.png) ### jar 打包、解開 * jar (`Java archive`) 命令用於打包 class 檔案(可多個),將其包裹到 JAR 檔,命令格式如下 ```shell= jar [options] [sourcefiles] ``` 常見的 option 如下 | jar option | 說明 | | - | - | | `-c` | 創建 JAR 檔案 | | `-v` | 輸出打包、解開時的詳細資訊 | | `-f` | 指定輸出的目錄 | | `-x` | 解開 JAR 檔案 | **範例**: 1. 停留在 `src/` 目錄下,並執行以下命令,打包一個 `myJar.jar` 檔案到 `deploy` 目錄下 ```shell= # 尚未編譯請先編譯 javac ../src/com/example/*.java -sourcepath ../src/ -d ../classes/ jar -cvf ./myJar.jar ../classes/com/example/*.class ``` > 可使用 `*.class` 代表該目錄下的所有文件 > ![](https://hackmd.io/_uploads/Sk3pn_YFh.png) 2. JAR 包也可以透過 `jar` 命令解開;以下將 `myJar.jar` 解開,並將內容放置當前目錄(沒有指定) ```shell= jar -xvf ./myJar.jar ``` > ![](https://hackmd.io/_uploads/ByzgadYFn.png) :::success * **`META-INF/MANIFEST.MF` 檔案** 該檔案描述了 JAR 檔的資訊 ```shell= Manifest-Version: 1.0 Created-By: 17.0.7 (JetBrains s.r.o.) ``` * 而我們前面有提到 `java` 命令可以指定 `-jar` 並找到對應的類執行,如果要達成這個條件就要稍微修改一下解開的 jar 包;步驟如下 1. **在 `classes` 目錄下創建 `Manifest.txt` 檔案** ```shell= touch classes/Manifest.txt ``` 2. 將主程式類別寫入 `Manifest.txt` 檔案 > 要寫入 **全類名**!(由於當前範例就是直接在 `java` 目錄下,所以很單純) ```shell= Main-Class: com.example.Main ``` :::danger * 坑:**必須以換行結束**! ::: 3. 重新打包 `classes` 目錄下的檔案,在這邊會 **用 `m` 來指定 MANIFEST** ```shell= jar -cvfm ../deploy/specificManifest.jar \ ./classes/Manifest.txt *.* ``` > ![](https://hackmd.io/_uploads/ryqrktFFn.png) * 測試使用 `java` 並指定 `jar` 執行 ```shell= java -jar /home/alien/IdeaProjects/java_learn/deploy/specificManifest.jar ``` ## JavaDoc 文件 Java 類可以**透過 JavaDoc 文件來對外公佈方法用法**;JavaDoc 文件是 **基於 HTML 格式** 的說明文件,而 JDK 也提供給我們產生說明檔的 `javadoc` 命令 在原始 Java 檔中,從 `/**` 註解到 `*/` 結束,內部包含的文件就會輸出到 HTML 中 :::info * 如果註解是在 `package` 描述前,那就會被 `javadoc` 命令忽略 ::: ```java= /** * <p><strong>This is main class</strong></p> */ public class TestMain { /** * @param args is input from user or other process. */ public static void main(String[] args) { System.out.println("Hello world!"); } } ``` > ![](https://hackmd.io/_uploads/SkdKrYYth.png) ### JavaDoc 標籤 * 可在註解中使用特殊符號幫助 JavaDoc 建立標籤,符號如下 | JavaDoc 標籤 | 描述 | | - | - | | `@version` | 指定版本資訊 | | `@since` | 可說明該方法、類最早出現的版本 | | `@author` | 指定作者 | | `@see` | 生成參考其他 JavaDoc 文檔 | | `@link` | 生成參考其他 JavaDoc 文件的連結(與 `@see` 差別在,它可嵌入到註解中,並為特定註解中的特定詞彙生成連結) | | `@deprecated` | 標示該方法、類已經被棄用 | | `@param` | 描述方法的參數(不可以用來描述類) | | `@return` | 描述方法的返回值 | | `@throws` | 描述方法拋出的例外 | 1. **`@see` 範例**: ```java= /** * <p><strong>This is main class</strong></p> */ public class TestMain { /** * @param args is input from user or other process. */ public static void main(String[] args) { System.out.println("Hello world!"); } /** * Default value is 0 */ private int value = 0; /** * @see #value show self param of `value`. * @see #setValue(int) Use this method to set value. */ public void showValue() { System.out.println("Value: " + value); } /** * Set new value for instance that TestMain. * @param value new value. */ public void setValue(int value) { this.value = value; } } ``` 執行以下命令生成檔案 ```shell= javadoc -public -protected -private -sourcepath src/ -d ./doc com.example ``` > ![](https://hackmd.io/_uploads/B1eLKtKt3.png) 2. **`@link` 範例**:與 `@see` 差別在,它可嵌入到 HTML 註解中形成連接,範例如下 ```java= /** * <p><strong>This is link class</strong></p> */ public class TestLink { boolean isStart = false; /** * <ul> * <li>if {@link #isStart isStart} is false, show sleeping.</li> * <li>if {@link #isStart isStart} is true, show working.</li> * </ul> */ public void showState() { if (isStart) { System.out.println("Start working."); } else { System.out.println("Sleeping."); } } } ``` 執行以下命令生成檔案 ```shell= javadoc -public -protected -private -sourcepath src/ -d ./doc com.example ``` > ![](https://hackmd.io/_uploads/ryP5itKY2.png) ### javadoc 命令 * javadoc 命令可以用來分析源碼並通過源碼的註解去生產 HTML,指令格式如下 ```shell= javadoc [options] [packagenames] <sourcefiles> ``` :::warning * 如果 `sourcefiles` 要使用目錄的話,須使用 `.` 替換 `/` 符號;如果是指定某個檔案就不用替換,繼續保持 `/` 符號 ::: 以下幾是 `javadoc` 常用的 Options | Options | 作用 | 是否預設 | | -------- | -------- | -------- | | `-public` | 只為 public 級別生成 Javadoc 文件 | N | | `-protected` | 只為 public、protected 級別生成 Javadoc 文件 | Y | | `-package` | 只為 public、protected、package 級別生成 Javadoc 文件 | N | | `-private` | 為所有級別生成 Javadoc 文件 | N | | `-version` | 解析 `@version` 標籤 | N | | `-author` | 解析 `@author` 標籤 | N | | `-splitindex` | 將索引分為每個字母對應一個索引檔 | N | | `-sourcepath <路徑>` | 指定 Java 原始碼路徑 | Y (預設為當前目錄) | | `-classpath <路徑>` | 指定 classpath | Y (預設為當前目錄) | | `-d <目錄>` | 指定 Javadoc 文件的輸出目錄 | Y (預設為當前目錄) | **範例**: 1. 生成指定指定文件 ```shell= javadoc -protected \ -sourcepath src/ \ -d ./doc \ src/com/example/TestLink.java ``` :::info * 如果有特別指定文件,那就 javadoc 不會自動處理別的文件 ::: > ![](https://hackmd.io/_uploads/ryway5tK3.png) 2. 透過 index 標記生成的檔案(方便尋找) ```shell= javadoc -splitindex \ -protected \ -sourcepath src/ \ -d ./doc \ com.example ``` > ![](https://hackmd.io/_uploads/r12e-5KF2.png) ## 更多的 Java 語言相關文章 ### Java 語言深入 * 在這個系列中,我們深入探討了 Java 語言的各個方面,從基礎類型到異常處理,從運算子到物件創建與引用細節。點擊連結了解更多! :::info * [**深入探索 Java 基礎類型、編碼、浮點數、參考類型和變數作用域 | 探討細節**](https://devtechascendancy.com/basic-types_encoding_reference_variables-scopes/) * [**深入了解 Java 應用與編譯:從原始檔到命令產出 JavaDoc 文件 | JDK 結構**](https://devtechascendancy.com/java-compilation_jdk_javadoc_jar_guide/) * [**深入理解 Java 異常處理:從基礎概念到最佳實踐指南**](https://devtechascendancy.com/java-jvm-exception-handling-guide/) * [**深入理解 Java 運算子與修飾符 | 重要概念、細節 | equals 比較**](https://devtechascendancy.com/java-operators-modifiers-key-concepts_equals/) * [**深入探索 Java 物件創建與引用細節:Clone 和finalize 特性,以及強、軟、弱、虛引用**](https://devtechascendancy.com/java-object-creation_jvm-reference-details/) ::: ### Java IO 相關文章 * 探索 Java IO 的奧秘,了解檔案操作、流處理、NIO等精彩內容! :::warning * [**Java File 操作指南:基礎屬性判斷、資料夾和檔案的創建、以及簡單示範**](https://devtechascendancy.com/java-file-operations-guide/) * [**深入探索 Java 編碼知識**](https://devtechascendancy.com/basic-types_encoding_reference_variables-scopes/) * [**深入理解 Java IO 操作:徹底了解流、讀寫、序列化與技巧**](https://devtechascendancy.com/deep-in-java-io-operations_stream-io/) * [**深入理解 Java NIO:緩衝、通道與編碼 | Buffer、Channel、Charset**](https://devtechascendancy.com/deep-dive-into-java-nio_buf-channel-charset/) ::: ### 深入 Java 物件導向 * 探索 Java 物件導向的奧妙,掌握介面、抽象類、繼承等重要概念! :::danger * [**深入比較介面與抽象類:從多個角度剖析**](https://devtechascendancy.com/comparing-interfaces-abstract-classes/) * [**深度探究物件導向:繼承的利與弊 | Java、Kotlin 為例 | 最佳實踐 | 內部類細節**](https://devtechascendancy.com/deep-dive-into-oop-inheritance/) * [**「類」的生命週期、ClassLoader 加載 | JVM 與 Class | Java 為例**](https://devtechascendancy.com/class-lifecycle_classloader-exploration_jvm/) ::: ## Appendix & FAQ :::info ::: ###### tags: `Java 基礎`