每位 Java 開發者每天都在寫 public static void main(String[] args),但能清晰說明每個關鍵字存在理由的人並不多。本文從 JVM 啟動機制、語言設計哲學、實際用法三個維度,徹底講透這個「最熟悉的陌生人」。
JVM 啟動時,通過反射調用主類的 main 方法。若非 public,JVM(位於不同模組/包)無法訪問,拋出 IllegalAccessError。這是「約定優於配置」的體現:固定方法名 + 固定修飾符,JVM 無需配置文件即可定位入口。
static 方法屬於類,無需對象即可調用:MainClass.main(args)static:編譯通過,運行報錯 NoSuchMethodError: main(JVM 找不到符合簽名的靜態方法)補充:方法代碼存儲在方法區,與 static 無關;static 決定的是調用方式(類調用 vs 實例調用)。
JVM 忽略 main 方法返回值。若返回 int,JVM 同樣報 NoSuchMethodError。若需返回退出碼,使用 System.exit(code)。
JVM 將命令列空格分隔的參數封裝為字串陣列傳入。注意:
java App "hello world" → args = ["hello world"]public class A {
public static void main(String[] args) {
System.out.println("A.main: " + Arrays.toString(args));
}
}
public class B {
public static void main(String[] args) {
A.main(new String[]{"1", "2", "3"}); // 直接調用,輸出: A.main: [1, 2, 3]
}
}
無需實例化,A.main(...) 即可。這在整合測試、批次腳本調用中極為實用。
// 啟動類
public class Launcher {
public static void main(String[] args) {
if (args.length == 0) { printUsage(); return; }
switch (args[0]) {
case "server" -> Server.main(Arrays.copyOfRange(args, 1, args.length));
case "client" -> Client.main(Arrays.copyOfRange(args, 1, args.length));
case "migrate" -> MigrationTool.main(Arrays.copyOfRange(args, 1, args.length));
default -> printUsage();
}
}
}
單一 JAR 包含多種啟動模式,通過第一個參數分發,簡化部署。
java Launcher arg1 arg2 │ ▼ JVM 啟動 → 類載入器載入 Launcher → 驗證/準備/解析 │ ▼ 查找 public static void main(String[]) 方法 │ ▼ 調用 Launcher.main(String[] args) // args = ["arg1", "arg2"] │ ▼ 方法返回 → JVM 正常退出 (exit code 0) │ ▼ 若拋出未捕獲異常 → JVM 非正常退出 (exit code 1) 並打印堆疊
| 誤區 | 正確認知 |
|---|---|
| main 必須在 public 類中 | 類可為 package-private,但 main 必須 public |
| args 永不為 null | 無參數時 args.length == 0,非 null |
| main 可拋出 checked exception | 簽名無 throws,只能拋出 unchecked 或內部捕獲 |
| main 返回 int 可作退出碼 | JVM 忽略返回值,必須用 System.exit() |
public static void main(String[] args) 是 JVM 與 Java 應用之間的「契約」:public 允許外部調用,static 免去實例化,void 簡化協議,String[] 攜帶外部輸入。理解其本質,能靈活應用於測試、多模式啟動、插件架構等場景。