精通 Java 命令列參數 — 從基礎到安全、實用的模式

目次

1. 介紹

本章目的

在 Java 中,命令列參數 是一項基本功能,讓程式在執行時能接收外部輸入並依此改變行為。本文將一步步帶你從 String[] args 的意義走向實務設計模式。在本章,我們首先說明 你可以做什麼 以及 為什麼這很重要

什麼是命令列參數?

Java 應用程式通常以具有以下簽名的 main 方法作為入口:

public class App {
    public static void main(String[] args) {
        // args is an array of strings passed at runtime
    }
}

args 參數是一個 String 陣列,用來儲存啟動指令所附帶的值。例如:

javac App.java
java App Tokyo 2025 debug

在此情況下,args 會包含 ["Tokyo", "2025", "debug"]
如果未提供任何參數,args.length0

使用情境

  • 切換環境或目標 — 例如生產/測試模式、區域代碼、語言或日誌等級。
  • 外部指定處理目標 — 檔案名稱、目錄、URL 或 ID 清單。
  • 自動化與批次處理 — 從 cron 工作或 CI/CD 流程傳遞日期範圍等參數。

所有這些都能讓程式在 不重新編譯 的情況下改變行為,使得命令列參數非常適合與 shell 腳本以及像 cron 這樣的工作排程器整合。

主要設計考量

  • 區分必填與選填參數 — 若缺少必填參數,顯示說明或以適當的狀態碼退出。
  • 提前驗證 — 盡早將字串轉換為數值或日期型別,並對無效輸入提供清晰的訊息。
  • 設計預設值 — 提供安全的預設值,使程式在省略選填參數時仍能執行。
  • 維持可讀性與可維護性 — 避免散落的原始陣列存取;在使用前將參數解析為結構化物件(DTO 或設定類別)。

在設定檔與環境變數之間的選擇

  • 命令列參數:最適合暫時性覆寫或工作特定的開關(視為最高優先權的本機設定)。
  • 環境變數:適用於機密資訊或依環境而異的設定,例如端點。
  • 設定檔(properties/JSON/YAML):在系統化管理多項設定、重複使用與版本控制時最為理想。

實務上,常會同時結合三者 — 設定檔 + 環境變數 + 參數 — 並讓參數擁有最高的覆寫優先權。

最小範例(列出所有參數)

public class ArgsEcho {
    public static void main(String[] args) {
        if (args.length == 0) {
            System.out.println("No arguments were provided.");
            System.out.println("Example: java ArgsEcho input.txt debug");
            return;
        }
        System.out.println("Received arguments:");
        for (int i = 0; i < args.length; i++) {
            System.out.printf("args[%d] = %s%n", i, args[i]);
        }
    }
}

接下來的內容(路線圖)

  • 基本操作 — 長度檢查、String[] args 的元素存取
  • 型別轉換 — int、double、boolean 的處理與例外安全
  • 選項式解析 — 例如 -v--help--mode=prod
  • IDE 設定 以及在測試時傳遞參數
  • 錯誤處理與安全性考量 — 無效輸入、例外情況
  • 實務範例 — 檔案處理、模式切換、日誌控制

首先,請記住這個原則:所有參數皆以字串形式接收,必須在使用前安全地轉換與驗證。以下章節將說明語法與常見模式,並提供詳細的程式碼範例。

2. 在 Java 中接收命令列參數

基本結構

在 Java 中,命令列參數以字串陣列 (String[] args) 的形式傳遞給 main 方法。執行指令中類別名稱之後,以空格分隔的每個字元串都會成為陣列中的一個元素。

public class Example {
    public static void main(String[] args) {
        System.out.println("Number of arguments: " + args.length);
        for (String arg : args) {
            System.out.println(arg);
        }
    }
}

當你以以下方式執行程式時:

javac Example.java
java Example apple orange banana

輸出將會是:

Number of arguments: 3
apple
orange
banana

存取特定參數

每個元素都可以透過其索引存取,索引從 0 開始。但請務必檢查 args.length,以避免 ArrayIndexOutOfBoundsException

public class AccessExample {
    public static void main(String[] args) {
        if (args.length < 2) {
            System.out.println("Usage: java AccessExample <name> <age>");
            return;
        }

        String name = args[0];
        String age  = args[1];
        System.out.println("Name: " + name);
        System.out.println("Age: " + age);
    }
}

當以 java AccessExample Alice 30 執行時,結果為:

Name: Alice
Age: 30

安全處理缺少的參數

由於 args 中的所有值都是字串,可能會缺失、格式錯誤,或無法轉換為預期的類型。使用前先驗證是良好的做法。

if (args.length == 0) {
    System.out.println("No arguments provided. Please specify input parameters.");
    System.exit(1); // Exit with an error code
}

你可以使用 System.exit(int) 來表示程式的退出狀態。依慣例,0 代表成功,非零值(如 12)則代表不同的錯誤類型。

引號與空白

以空格分隔的參數會被視為不同的值。如果需要在單一參數中包含空格,請使用雙引號將其包住:

java Example "New York" Japan

這將產生:

args[0] = New York
args[1] = Japan

當未提供任何參數時

如果未提供參數,args.length0。你可以利用此條件分支程式邏輯,例如:

if (args.length == 0) {
    System.out.println("Running in interactive mode...");
} else {
    System.out.println("Running with parameters...");
}

此模式在同時支援互動式與批次執行模式的工具中特別有用。

3. 資料轉換與錯誤處理

所有命令列參數皆以字串 (String) 形式傳遞。因此,若要將它們作為數字、布林或其他類型使用,必須明確轉換。本章說明如何安全地轉換資料類型並處理可能的錯誤。

將字串轉換為整數與浮點數

Integer.parseInt()Double.parseDouble() 方法用於將字串值轉換為數值型別。如果輸入無法解析為數字,將拋出 NumberFormatException

public class ParseExample {
    public static void main(String[] args) {
        if (args.length < 2) {
            System.out.println("Usage: java ParseExample <price> <quantity>");
            return;
        }

        try {
            double price = Double.parseDouble(args[0]);
            int quantity = Integer.parseInt(args[1]);
            System.out.println("Total: " + (price * quantity));
        } catch (NumberFormatException e) {
            System.out.println("Error: Please enter numeric values only.");
        }
    }
}

執行範例:

java ParseExample 120.5 3
Total: 361.5

處理布林值

要解析像「除錯模式」或「詳細」之類的布林旗標,可使用 Boolean.parseBoolean()。只有當參數等於「true」(不分大小寫)時才會回傳 true

boolean debug = false;
if (args.length > 0) {
    debug = Boolean.parseBoolean(args[0]);
}

if (debug) {
    System.out.println("Debug mode enabled");
} else {
    System.out.println("Debug mode disabled");
}

Execution example:

java Example true
Debug mode enabled

java Example false
Debug mode disabled

使用預設值的安全轉換

在輸入缺失或無效時提供預設值是一個良好的做法。這可防止執行時錯誤並提升使用者體驗。

public static int parseIntOrDefault(String s, int defaultValue) {
    try {
        return Integer.parseInt(s);
    } catch (Exception e) {
        return defaultValue;
    }
}

此模式也可根據需求擴展至浮點數或日期。

優雅地捕捉與回報錯誤

在處理使用者輸入時,錯誤訊息應該要清晰且具幫助性。與其僅僅印出堆疊追蹤,不如提供正確使用方式的指引。

try {
    int age = Integer.parseInt(args[0]);
    if (age < 0) throw new IllegalArgumentException("Age cannot be negative");
    System.out.println("Age: " + age);
} catch (NumberFormatException e) {
    System.err.println("Error: Please enter a valid number for age.");
} catch (IllegalArgumentException e) {
    System.err.println(e.getMessage());
}

使用 System.err.println() 可將錯誤訊息導向標準錯誤流,從而在日誌或管道中與正常輸出分離。

可選:使用 Java 的 Optional 類別

為避免 null 檢查並提升可讀性,考慮在參數解析時使用 Optional<T>。例如:

import java.util.Optional;

public class OptionalExample {
    public static void main(String[] args) {
        Optional<String> arg0 = (args.length > 0) ? Optional.of(args[0]) : Optional.empty();
        String message = arg0.orElse("default");
        System.out.println("Argument: " + message);
    }
}

即使未提供任何參數,亦能確保程式安全執行。

總結: 要打造穩健的命令列程式,必須假設使用者輸入可能缺失或格式錯誤。結合解析、驗證與具意義的錯誤訊息,以確保穩定性。

4. 處理選項式參數

隨著 Java 程式的規模擴大,處理形如 -h--help--mode=prod 的參數變得必不可少。這類選項式參數讓程式更易讀且對使用者更友善,特別是針對命令列工具或自動化腳本。

短選項與長選項

選項通常有兩種形式:

  • 短選項 — 以單一連字號為前綴,例如 -v-h
  • 長選項 — 以雙連字號為前綴,例如 --help--mode=prod

您可以使用 startsWith()split() 等字串操作手動解析它們。

public class OptionExample {
    public static void main(String[] args) {
        boolean help = false;
        String mode = "dev";

        for (String arg : args) {
            if (arg.equals("-h") || arg.equals("--help")) {
                help = true;
            } else if (arg.startsWith("--mode=")) {
                mode = arg.split("=", 2)[1];
            }
        }

        if (help) {
            System.out.println("Usage: java OptionExample [--mode=<mode>] [-h|--help]");
            return;
        }

        System.out.println("Mode: " + mode);
    }
}

執行範例:

java OptionExample
Mode: dev

java OptionExample --mode=prod
Mode: prod

java OptionExample -h
Usage: java OptionExample [--mode=<mode>] [-h|--help]

結合旗標與值

有時需要處理帶有獨立值的旗標,例如 --input data.txt。在此情況下,您可以使用索引遍歷參數,安全地讀取下一個值。

public class InputExample {
    public static void main(String[] args) {
        String inputFile = null;

        for (int i = 0; i < args.length; i++) {
            if (args[i].equals("--input") && i + 1 < args.length) {
                inputFile = args[i + 1];
            }
        }

        if (inputFile == null) {
            System.out.println("Please specify an input file using --input <filename>");
            return;
        }

        System.out.println("Processing file: " + inputFile);
    }
}

執行範例:

java InputExample --input report.csv
Processing file: report.csv

結合多個選項

實際工具通常接受多個選項,例如 --mode=prod --debug --log-level=2。為了乾淨地管理這些選項,建議將所有選項解析為 設定物件

class Config {
    String mode = "dev";
    boolean debug = false;
    int logLevel = 1;
}

public class ConfigExample {
    public static void main(String[] args) {
        Config cfg = new Config();

        for (String arg : args) {
            if (arg.startsWith("--mode=")) {
                cfg.mode = arg.split("=", 2)[1];
            } else if (arg.equals("--debug")) {
                cfg.debug = true;
            } else if (arg.startsWith("--log-level=")) {
                try {
                    cfg.logLevel = Integer.parseInt(arg.split("=", 2)[1]);
                } catch (NumberFormatException e) {
                    System.err.println("Invalid log level. Using default value: 1");
                }
            }
        }

        System.out.println("Mode: " + cfg.mode);
        System.out.println("Debug: " + cfg.debug);
        System.out.println("Log Level: " + cfg.logLevel);
    }
}

使用 Apache Commons CLI(推薦)

對於較大的專案,手動解析參數容易出錯。Apache Commons CLI 函式庫提供了一種標準化的方式來定義與處理命令列選項,並支援說明訊息與驗證。

import org.apache.commons.cli.*;

public class CLIExample {
    public static void main(String[] args) throws Exception {
        Options options = new Options();
        options.addOption("h", "help", false, "Show help message");
        options.addOption("m", "mode", true, "Execution mode (dev/prod)");

        CommandLineParser parser = new DefaultParser();
        CommandLine cmd = parser.parse(options, args);

        if (cmd.hasOption("h")) {
            HelpFormatter formatter = new HelpFormatter();
            formatter.printHelp("CLIExample", options);
            return;
        }

        String mode = cmd.getOptionValue("m", "dev");
        System.out.println("Mode: " + mode);
    }
}

此方法會自動支援說明產生、輸入驗證,以及在定義與邏輯之間更清晰的程式碼分離。

最佳實踐摘要

  • 同時支援短旗 ( -h ) 與長旗 ( --help )。
  • 使用 startsWith()split("=") 進行簡易解析。
  • 使用設定類別以提升可維護性。
  • 採用像 Apache Commons CLI 之類的函式庫以實作可擴充的解決方案。
  • 始終提供 --help 或使用說明輸出以確保清晰。

透過這樣設計參數解析方式,您可以讓 Java 工具更加直覺、可預測且易於維護——就像專業的命令列應用程式一樣。

5. 在 IDE 中設定與測試命令列參數

在開發 Java 應用程式時,您常常會想測試程式在不同命令列參數下的行為,而不必直接在終端機執行。本節說明如何在常見的 IDE(如 EclipseIntelliJ IDEA)中設定參數。

在 Eclipse 中設定參數

在 Eclipse 中,您可以透過「Run Configurations」對話框設定參數。請依照以下步驟:

  • 1️⃣ 開啟你的 Java 檔案,於編輯器上點右鍵。
  • 2️⃣ 選擇 Run As → Run Configurations…
  • 3️⃣ 在對話框中,於「Java Application」下選取你的類別。
  • 4️⃣ 點擊 Arguments 分頁。
  • 5️⃣ 在 Program arguments 方框中,輸入以空格分隔的欲傳入參數。

範例:

Tokyo 2025 debug

執行程式時,Eclipse 會自動將這些參數傳遞給 String[] args

小技巧: 你可以建立多個設定——例如「production mode」與「debug mode」——並輕鬆切換。

在 IntelliJ IDEA 中設定參數

在 IntelliJ IDEA 中,步驟同樣簡單:

  • 1️⃣ 點擊 Run 按鈕旁的下拉選單(右上角)。
  • 2️⃣ 選擇 Edit Configurations…
  • 3️⃣ 在「Run/Debug Configurations」視窗中,於「Application」下找到你的 Java 類別。
  • 4️⃣ 在 Program arguments 欄位中,輸入與命令列相同的參數。

範例:

--mode=prod --debug true

點擊 Apply 後再點 Run。IntelliJ 會以這些參數啟動程式,傳遞給 args 陣列。

快速測試多種模式

在測試自動化或批次工具時,你可以註冊多個執行設定,分別帶入不同的參數組合,例如:

  • config-dev--mode=dev --debug
  • config-prod--mode=prod --log-level=2
  • config-localinput.txt output.txt

如此即可一鍵切換測試條件,無需修改原始碼或終端機指令。

在 JUnit 測試中傳遞參數

若需在自動化測試中驗證參數處理,可在 JUnit 內顯式傳入 String[]main 方法。

import org.junit.jupiter.api.Test;

public class ArgsTest {
    @Test
    void testArguments() {
        String[] args = {"--mode=prod", "--debug"};
        MyApp.main(args);
    }
}

此模式讓你在與一般應用程式相同的 JVM 環境下測試程式邏輯,便於完整的 CI/CD 自動化。

IDE 參數測試常見陷阱

  • 🧩 執行前忘記儲存設定(尤其在 Eclipse 中)。
  • 🧩 空格打錯——每個空格會分割參數,除非使用引號包住。
  • 🧩 編輯參數後未重新執行——某些 IDE 會快取舊的設定。
  • 🧩 期待環境變數會自動變更(需在 IDE 設定中另行配置)。

掌握這些 IDE 參數設定後,你即可在本機模擬與正式環境相同的行為,減少意外的執行時問題。

6. 錯誤處理與安全設計

在接受命令列輸入時,Java 程式必須優雅地處理無效、意外或惡意的參數。本節說明安全驗證做法與防止系統失效或濫用的安全原則。

使用前先驗證

絕不要假設使用者輸入是正確的。使用前務必驗證參數,無論是計算、檔案操作或系統呼叫。常見的驗證項目包括:

  • 檢查 參數數量args.length)。
  • 驗證 格式(例如數值、布林、URL 或日期)。
  • 確認 檔案路徑 是否存在且可存取。
  • 拒絕 無效字元,避免注入或路徑穿越。

範例:在使用前驗證數值輸入範圍

try {
    int threads = Integer.parseInt(args[0]);
    if (threads < 1 || threads > 64) {
        throw new IllegalArgumentException("Thread count must be between 1 and 64.");
    }
    System.out.println("Using " + threads + " threads.");
} catch (Exception e) {
    System.err.println("Error: " + e.getMessage());
}

防止檔案路徑濫用

當參數代表檔案路徑時,必須確保使用者無法利用 ../ 或符號連結跳出預期目錄。例如:

import java.nio.file.*;

Path baseDir = Paths.get("/var/app/data");
Path inputPath = baseDir.resolve(args[0]).normalize();

if (!inputPath.startsWith(baseDir)) {
    throw new SecurityException("Access outside of permitted directory is not allowed.");
}

這可防止使用者嘗試存取指定區域之外的敏感檔案的路徑遍歷攻擊。

避免執行任意指令

絕不可在未經清理的情況下直接將參數傳遞給系統指令或外部程序。否則,您的程式可能會受到 指令注入 的威脅。

// ❌ Dangerous example (do not use)
Runtime.getRuntime().exec("cat " + args[0]);

// ✅ Safe alternative using ProcessBuilder
ProcessBuilder pb = new ProcessBuilder("cat", args[0]);
pb.redirectErrorStream(true);
pb.start();

ProcessBuilder API 會將每個參數分別處理,防止惡意的 shell 解析。

錯誤回報與退出代碼

對於專業工具而言,應設計清晰的退出代碼與錯誤訊息,以協助使用者了解問題所在。範例分類:

  • 0 — 成功執行
  • 1 — 無效的輸入或參數錯誤
  • 2 — 檔案或資源遺失
  • 3 — 權限被拒絕
  • 99 — 未知或未處理的例外

範例實作:

try {
    // business logic
} catch (IllegalArgumentException e) {
    System.err.println("Invalid argument: " + e.getMessage());
    System.exit(1);
} catch (SecurityException e) {
    System.err.println("Permission error: " + e.getMessage());
    System.exit(3);
} catch (Exception e) {
    e.printStackTrace();
    System.exit(99);
}

清理日誌與錯誤輸出

在記錄使用者提供的參數時,請勿包含密碼、令牌或個人資料等敏感資訊。範例:

String password = args[0];
// ❌ Don't log this directly
// System.out.println("Password: " + password);

// ✅ Use placeholders or masked output
System.out.println("Password provided: [REDACTED]");

此作法可防止在日誌或主控台輸出中意外洩漏資料,尤其在共享環境或 CI/CD 流程中更為重要。

防禦性程式設計摘要

  • 始終在使用前驗證參數。
  • 正規化並檢查路徑以防止目錄遍歷。
  • 絕不將使用者輸入串接至 shell 指令。
  • 設計清晰的退出代碼以符合自動化需求。
  • 在日誌與訊息中遮蔽敏感資料。
  • 快速失敗但安全失敗 — 避免程式崩潰而洩漏堆疊追蹤。

透過套用這些防禦技術,您的 Java 應用程式將保持韌性、安全且具專業水準,即使在不可預測的執行環境中執行亦能如常運作。

7. 實作範例 — 檔案處理、模式切換與日誌控制

在了解參數語法與最佳實踐後,接下來就可以探索實務案例。本節將介紹三種常見模式:檔案操作、環境模式切換與動態日誌控制。這些情境在真實應用與自動化工作流程中相當常見。

範例 1:檔案處理程式

在許多自動化腳本中,命令列參數用於指定輸入與輸出檔案的路徑。以下是一個簡單範例,將一個檔案的內容複製到另一個檔案:

import java.nio.file.*;
import java.io.IOException;

public class FileCopy {
    public static void main(String[] args) {
        if (args.length < 2) {
            System.out.println("Usage: java FileCopy <source> <destination>");
            System.exit(1);
        }

        Path src = Paths.get(args[0]);
        Path dst = Paths.get(args[1]);

        try {
            Files.copy(src, dst, StandardCopyOption.REPLACE_EXISTING);
            System.out.println("File copied successfully: " + dst);
        } catch (IOException e) {
            System.err.println("File copy failed: " + e.getMessage());
            System.exit(2);
        }
    }
}

Execution example:

java FileCopy input.txt backup/input_copy.txt

By parameterizing file paths, you can reuse this program in automation pipelines, backup scripts, or cron jobs.

Example 2: Switching Between Modes (Development / Production)

Applications often behave differently depending on their environment — for example, using different databases or API endpoints. You can switch behavior dynamically using an argument like --mode=prod.

public class ModeSwitch {
    public static void main(String[] args) {
        String mode = "dev"; // default mode

        for (String arg : args) {
            if (arg.startsWith("--mode=")) {
                mode = arg.split("=", 2)[1];
            }
        }

        switch (mode) {
            case "dev":
                System.out.println("Running in Development Mode");
                break;
            case "prod":
                System.out.println("Running in Production Mode");
                break;
            case "test":
                System.out.println("Running in Test Mode");
                break;
            default:
                System.err.println("Unknown mode: " + mode);
                System.exit(1);
        }
    }
}

Execution examples:

java ModeSwitch --mode=dev
Running in Development Mode

java ModeSwitch --mode=prod
Running in Production Mode

This design allows you to manage multiple configurations cleanly and avoid hardcoding environment-specific logic.

Example 3: Log Level and Debug Control

Logging levels are often controlled via command-line arguments, enabling flexible diagnostics without code changes.

public class LogControl {
    public static void main(String[] args) {
        int logLevel = 1; // 1: normal, 2: verbose, 3: debug

        for (String arg : args) {
            if (arg.startsWith("--log=")) {
                try {
                    logLevel = Integer.parseInt(arg.split("=", 2)[1]);
                } catch (NumberFormatException e) {
                    System.err.println("Invalid log level. Using default: 1");
                }
            }
        }

        if (logLevel >= 3) System.out.println("[DEBUG] Debug information enabled");
        if (logLevel >= 2) System.out.println("[INFO] Detailed information shown");
        System.out.println("[NORMAL] Application started");
    }
}

Execution example:

java LogControl --log=3
[DEBUG] Debug information enabled
[INFO] Detailed information shown
[NORMAL] Application started

This pattern is common in production tools where logging verbosity must be adjusted dynamically without recompilation.

Combining All Patterns

You can combine these examples into a single configurable tool that handles multiple responsibilities. For instance, a file-processing program that accepts --mode, --log, and --input options simultaneously.

java App --mode=prod --log=2 --input data.txt

By structuring your argument parsing carefully, you can create flexible and reusable command-line utilities suitable for real deployment environments.

Summary of Practical Patterns

  • ✅ 使用參數以提升檔案輸入/輸出彈性。
  • ✅ 允許在開發、測試與正式環境之間切換模式。
  • ✅ 從命令列啟用日誌與除錯控制。
  • ✅ 結合這些參數以打造多功能自動化工具。

這些範例代表了現代 Java 自動化工具的基礎——輕量化、參數化,且易於與腳本或排程器整合。

8. 真實環境部署的最佳實踐

當您的 Java 應用程式開始在正式環境中使用時,持續且安全地處理命令列參數就成為專業軟體設計的一環。本節彙總了在真實環境中可維護、可靠且具擴充性的參數處理最佳實踐。

1. 保持介面一致性

發布後,每個命令列參數的意義應保持穩定。除非絕對必要,避免重新命名或移除既有選項。新增參數時,應透過保持預設行為不變來確保向後相容性。

// Old version
java ReportTool --mode=prod

// New version (compatible)
java ReportTool --mode=prod --log=2

此做法可避免破壞依賴此工具的自動化腳本、CI 流程或 cron 工作。

2. 提供說明選項

每個專業的命令列工具都應提供可直接取得的 --help-h 旗標,以說明使用方式、可用選項與範例。

if (args.length == 0 || Arrays.asList(args).contains("--help")) {
    System.out.println("Usage: java MyTool [options]");
    System.out.println("  --input <file>     Specify input file");
    System.out.println("  --mode <type>      Choose mode: dev, test, prod");
    System.out.println("  --log <level>      Set log verbosity (1-3)");
    System.exit(0);
}

此舉不僅提升可用性,也能減少使用者錯誤與支援請求。

3. 清晰記錄參數行為

維護一份最新的 README 或線上文件,列出所有支援的參數、預設值與範例執行。當多個選項相互作用時(例如 --mode=prod 會停用除錯),應明確說明這些關係。

# Example documentation section

### Options
--mode=<value>     Select execution mode (dev/test/prod)
--log=<level>      Verbosity (1: normal, 2: verbose, 3: debug)
--input=<path>     Input file path

### Example
java MyTool --mode=prod --log=2 --input report.csv

4. 將設定與程式碼分離

不要將運作參數硬編碼。應使用設定檔或環境變數來存放敏感資料或預設值,並在需要時讓命令列參數覆寫它們。

String defaultMode = System.getenv().getOrDefault("APP_MODE", "dev");
String mode = defaultMode;

// CLI arguments override environment variable
for (String arg : args) {
    if (arg.startsWith("--mode=")) {
        mode = arg.split("=", 2)[1];
    }
}
System.out.println("Running in " + mode + " mode");

此結構讓開發者與運維人員皆能在不重新編譯或修改程式碼的情況下調整行為。

5. 同時支援短式與長式參數

同時提供短式 (-v) 與長式 (--verbose) 選項,可提升不同使用者的便利性:

if (arg.equals("-v") || arg.equals("--verbose")) {
    verbose = true;
}

此雙形式亦符合 UNIX/Linux 的慣例,提升有經驗工程師的可用性。

6. 回傳有意義的退出代碼

Jenkins、Shell 腳本或編排系統等整合工具皆依賴程序的退出代碼。使用明確的不同代碼來表示成功、警告與錯誤。例如:

  • 0 — 成功
  • 10 — 缺少必要參數
  • 20 — 驗證錯誤
  • 30 — 執行時例外

這讓外部自動化能夠智慧回應——例如僅在可恢復的錯誤時重試。

7. 安全記錄參數與環境

在除錯生產環境問題時,了解傳入的參數是至關重要的。然而,您應該謹慎記錄它們:

  • 遮蔽敏感值,例如密碼或令牌( ****** )。
  • 僅記錄安全且非個人化的參數。
  • 包含時間戳記與程序識別碼。

範例安全日誌輸出:

[2025-11-11 09:30:15] App started
Mode: prod
Log level: 2
Input: data.csv
Password: [REDACTED]

8. 使用函式庫以提升可擴充性

對於大型工具,避免手動字串解析,而是使用以下函式庫:

  • Apache Commons CLI — 簡單且成熟。
  • Picocli — 現代、基於註解,且支援彩色說明輸出。
  • JCommander — 直觀且輕量的參數綁定函式庫。

範例(Picocli):

import picocli.CommandLine;
import picocli.CommandLine.Option;

public class App implements Runnable {
    @Option(names = {"-m", "--mode"}, description = "Execution mode")
    String mode = "dev";

    @Option(names = {"-l", "--log"}, description = "Log level")
    int log = 1;

    public void run() {
        System.out.println("Mode: " + mode + ", Log: " + log);
    }

    public static void main(String[] args) {
        new CommandLine(new App()).execute(args);
    }
}

像 Picocli 這樣的函式庫能大幅減少樣板程式碼,提供自動驗證,並自動產生說明訊息。

9. 為國際化做準備

如果您的應用程式面向全球使用者,請在設計說明訊息與日誌時考慮本地化。使用資源束(.properties 檔案)來存放訊息,而非硬編碼英文文字。

ResourceBundle bundle = ResourceBundle.getBundle("messages", Locale.getDefault());
System.out.println(bundle.getString("usage.help"));

這讓您的程式能根據系統語系自動切換語言。

10. 為參數邏輯自動化測試

確保參數解析已被單元測試覆蓋,以防在新增或修改選項時產生回歸。

@Test
void testModeArgument() {
    String[] args = {"--mode=prod"};
    assertDoesNotThrow(() -> MyApp.main(args));
}

自動化測試提供信心,確保您的 CLI 在更新與重構後仍保持穩定。

小結

  • 保持向後相容的參數結構。
  • 提供清晰的 --help 說明文件。
  • 將設定與程式碼分離以提升彈性。
  • 使用函式庫與自動化以確保可靠性與可維護性。
  • 從一開始就考慮國際化與安全性。

遵循這些指引,您的 Java 命令列工具將在本地與全球環境中皆具備專業級的穩定性與可用性。

9. 小結與設計範本

在本文中,我們探討了 Java 命令列參數的完整生命週期——從基本語法到實務安全與部署考量。讓我們總結核心概念,並提供一個可重複使用的設計範本,讓您能套用於自己的專案。

主要重點

  • 基本結構:main 方法中使用 String[] args 來接收參數。
  • 驗證: 必須檢查缺少或無效的輸入,並提供有用的回饋。
  • 轉換: 安全地將字串參數轉換為數值、布林或自訂類型。
  • 選項解析: 同時支援短式( -h )與長式( --help )選項以提升可讀性。
  • 安全性: 正規化路徑、清理輸入,避免不安全的指令執行。
  • 實務應用: 使用參數進行檔案處理、模式控制與日誌設定。
  • 專業實踐: 提供文件、統一介面與退出代碼。
  • 可擴充性: 在較大型專案中使用 Picocli 或 Commons CLI 等函式庫。
  • 自動化: 透過 JUnit 或 CI 流程測試參數處理。

可重複使用的設計範本

以下範本將本指南中討論的最佳實踐(驗證、說明顯示、環境模式處理與日誌等級)整合於一個簡潔的程式中。

import java.util.*;

public class AppTemplate {

    static class Config {
        String mode = "dev";
        int logLevel = 1;
        String input = null;
        boolean help = false;
    }

    public static void main(String[] args) {
        Config cfg = parseArgs(args);

        if (cfg.help) {
            printHelp();
            System.exit(0);
        }

        // Logging example
        if (cfg.logLevel >= 3) System.out.println("[DEBUG] Mode = " + cfg.mode);
        if (cfg.logLevel >= 2) System.out.println("[INFO] Log level set to " + cfg.logLevel);

        if (cfg.input != null) {
            System.out.println("[INFO] Processing input file: " + cfg.input);
        } else {
            System.out.println("[WARN] No input file specified. Running default mode.");
        }

        // Main logic
        System.out.println("Running in " + cfg.mode + " mode.");
    }

    private static Config parseArgs(String[] args) {
        Config cfg = new Config();
        for (String arg : args) {
            if (arg.equals("-h") || arg.equals("--help")) {
                cfg.help = true;
            } else if (arg.startsWith("--mode=")) {
                cfg.mode = arg.split("=", 2)[1];
            } else if (arg.startsWith("--log=")) {
                try {
                    cfg.logLevel = Integer.parseInt(arg.split("=", 2)[1]);
                } catch (NumberFormatException e) {
                    System.err.println("Invalid log level, using default (1).");
                }
            } else if (arg.startsWith("--input=")) {
                cfg.input = arg.split("=", 2)[1];
            }
        }
        return cfg;
    }

    private static void printHelp() {
        System.out.println("Usage: java AppTemplate [options]");
        System.out.println("Options:");
        System.out.println("  --mode=<dev|test|prod>    Execution mode (default: dev)");
        System.out.println("  --log=<1|2|3>              Log level (1:normal, 2:verbose, 3:debug)");
        System.out.println("  --input=<file>             Input file path");
        System.out.println("  -h, --help                 Show this help message");
    }
}

此設計提供:

  • 清晰的參數解析,與業務邏輯分離。
  • 自動顯示說明。
  • 安全的數值轉換與預設值。
  • 簡易的日誌控制,支援除錯與正式環境。

擴充範本

您可以從以下幾個方向擴充此基礎範本:

  • 加入檔案存在性檢查與例外處理。
  • 整合 Properties 或 JSON 設定檔。
  • 支援子指令(例如 java Tool analyzejava Tool export)。
  • 實作彩色終端輸出或結構化日誌。
  • 以環境變數作為缺失參數的預設值。

透過結合上述增強功能,您可以將此輕量結構演進為符合專案需求的堅固 CLI 框架。

最後的話

命令列參數雖看似簡單,但它們構成可配置、可測試與可自動化軟體的基礎。請以同樣的細心設計它們,正如您對 API 介面所做的 — 清晰、可預測且安全。

簡而言之:投入精力於結構良好的參數設計,將在開發的各個階段帶來回報 — 從除錯與自動化,到部署與長期維護。

憑藉這些原則與範本,您現在可以設計專業級的命令列工具,並在不同環境、團隊與多年演進中保持一致的行為。

常見問題解答

本章節總結了開發者在 Java 中處理命令列參數時常見的問題。每個答案都包含簡短範例或實用指引。

Q1. 如何同時處理可選與必需的參數?

必需參數應該明確驗證,例如檢查 args.length 或特定旗標的存在。可選參數可以設置安全的預設值。

if (args.length < 1) {
    System.err.println("Error: Missing input file");
    System.exit(1);
}

String input = args[0];
String mode = (args.length > 1) ? args[1] : "dev";

在較大的專案中,建議使用像 PicocliApache Commons CLI 這類支援必需/可選旗標的函式庫來定義參數結構。

Q2. 如何在參數中包含空格(例如檔名或短語)?

在終端機執行時,請將參數用雙引號包住:

java Example "New York City" Japan

Output:

args[0] = New York City
args[1] = Japan

這樣可確保整個短語被視為單一參數,而非多個單字。

Q3. 若未提供任何參數,會發生什麼?

若未傳入任何參數,args.length0。您可以安全地偵測並處理此情況,例如顯示說明訊息或使用預設值。

if (args.length == 0) {
    System.out.println("No arguments provided. Running default mode...");
}

Q4. 如何在 IntelliJ 或 Eclipse 等 IDE 中測試參數?

兩個 IDE 都提供程式參數的設定對話方塊:

  • Eclipse: Run → Run Configurations → Arguments tab → 輸入參數。
  • IntelliJ IDEA: Run → Edit Configurations → Program arguments 欄位。

範例:--mode=prod --log=2 --input=data.txt

Q5. 如何處理布林旗標,例如 --debug--verbose

布林旗標通常不帶值。您可以使用 equals()contains() 方法來偵測它們。

boolean debug = false;
for (String arg : args) {
    if (arg.equals("--debug") || arg.equals("-d")) {
        debug = true;
    }
}

if (debug) System.out.println("Debug mode enabled.");

Q6. 如何將多個參數傳遞給 java -jar 應用程式?

執行已封裝的 JAR 檔時,請將參數放在 JAR 名稱之後。範例:

java -jar MyApp.jar --mode=prod input.txt --log=3

應用程式的 main(String[] args) 會收到與標準執行相同的參數。

Q7. 如何從設定檔讀取參數,而非從命令列?

您可以使用 Java 的 PropertiesYAML/JSON 函式庫載入預設設定,若有指定 CLI 參數則以其覆寫。

Properties props = new Properties();
props.load(new FileInputStream("config.properties"));
String mode = props.getProperty("mode", "dev");

// CLI overrides file
for (String arg : args) {
    if (arg.startsWith("--mode=")) {
        mode = arg.split("=", 2)[1];
    }
}

Q8. 可以在參數中使用 Unicode 或非 ASCII 字元嗎?

可以,Java 完全支援在 args 中使用 Unicode 字串。然而,您的終端機或作業系統編碼也必須支援所使用的字元。在 Windows 上,請考慮以 UTF-8 位置 (chcp 65001) 執行;在 Linux/macOS 上,確保 shell 使用 UTF-8 編碼。

Q9. 如何防止使用者提供的參數造成安全問題?

  • ✅ 驗證所有輸入(數值範圍、檔案路徑、URL)。
  • ✅ 正規化路徑以防止目錄穿越(../)。
  • ✅ 切勿將使用者輸入串接至 shell 指令。
  • ✅ 使用白名單或正則表達式進行嚴格驗證。

對於生產工具,請考慮拒絕或轉義可能觸發 shell 執行的字元,例如 ;|&&

Q10. 我還應該手動使用 args 嗎?

對於小型工具,手動使用 String[] args 解析是可行的。若是長期或企業級工具,建議使用專用函式庫:

  • Picocli — 基於註解,易於整合。
  • Apache Commons CLI — 經過實戰驗證的經典函式庫。
  • JCommander — 簡單且輕量級。

使用函式庫可以減少錯誤、提升可讀性,並提供內建的說明與驗證功能。

Q11. 如何輕鬆列印所有接收到的參數?

System.out.println("Received arguments:");
for (int i = 0; i < args.length; i++) {
    System.out.printf("args[%d] = %s%n", i, args[i]);
}

此程式碼片段非常適合用來除錯參數解析邏輯。

Q12. 我可以同時使用參數與環境變數嗎?

可以。環境變數非常適合用於系統層級的設定(例如 API 金鑰),而命令列參數則最適合用於臨時的覆寫。

String apiKey = System.getenv().getOrDefault("API_KEY", "none");
for (String arg : args) {
    if (arg.startsWith("--api=")) {
        apiKey = arg.split("=", 2)[1];
    }
}
System.out.println("API Key: " + (apiKey.equals("none") ? "not set" : "[REDACTED]"));

這種分層的設定模型讓您的軟體既具彈性又保持安全。

Q13. 如何優雅地處理不正確的參數類型?

使用 try-catch 區塊,並提供有意義的錯誤訊息,避免程式崩潰:

try {
    int threads = Integer.parseInt(args[0]);
} catch (NumberFormatException e) {
    System.err.println("Invalid number format: " + args[0]);
    System.exit(1);
}

這樣可確保程式乾淨結束,並協助使用者快速修正輸入錯誤。

Q14. 我可以為說明或錯誤訊息顯示彩色輸出嗎?

可以,您可以在大多數終端機中使用 ANSI 跳脫碼來實現彩色輸出:

final String RED = "u001B[31m";
final String RESET = "u001B[0m";
System.err.println(RED + "Error: Invalid argument" + RESET);

PicocliJansi 這類函式庫能自動處理,且具跨平台相容性。

Q15. 如何更有效率地除錯參數解析?

加入一個「診斷」模式,使用 --debug--trace 旗標,在啟動時列印所有內部狀態。例如:

if (Arrays.asList(args).contains("--debug")) {
    System.out.println("[TRACE] Arguments: " + Arrays.toString(args));
}

在生產環境中排除自動化或設定問題時,這非常有幫助。