跳转至

实例 main 与紧凑源文件(JEP 512)#

版本信息

为什么需要#

JDK 8 的程序入口必须长这样:

public class App {
    public static void main(String[] args) { /* ... */ }
}

对初学者和小脚本来说门槛偏高:要懂 publicstaticString[] args,还要裹一层类。JEP 512 把入口放宽——main 可以是实例、无参、非 static 的方法;进一步地,紧凑源文件(compact source file)允许单文件程序省略类声明,直接写 void main() { ... }

语法#

实例 main 方法(本仓库 examples/instance-main/ 真实源码,snippets 嵌入零漂移):

package com.javamodern.instancemain;

/**
 * 演示 JDK 25 实例 main 方法(JEP 512):程序入口 {@code main} 可以是
 * <b>实例、无参、非 static</b> 的方法(此前必须是 {@code public static void main(String[])})。
 *
 * <p>仍保留可测试的逻辑方法 {@link #greet()},供单元测试与实例 main 共用。
 * 运行方式:{@code java InstanceMainDemo}(JDK 25 launcher 会找到实例 {@code main()})。
 */
public class InstanceMainDemo {

    /** 问候语(逻辑方法,供测试与实例 main 共用)。 */
    String greet() {
        return "Hello, Java 25!";
    }

    void main() { // (1) JDK 25:实例、无参、非 static 的 main 是合法程序入口
        System.out.println(greet());
    }
}
  1. :material-lightbulb: void main()实例、无参、非 static 的方法——JDK 25 起它就是合法的程序入口。launcher 会自动 new InstanceMainDemo() 再调用它。

运行方式与输出(JDK 25 实测):

java -cp ... com.javamodern.instancemain.InstanceMainDemo
# 输出:Hello, Java 25!

更激进的 紧凑源文件——整个文件没有类声明,只有 void main()

void main() {
    String name = "Java 25";
    System.out.println("Hello from " + name);
}

保存为 Hi.java,直接跑(JDK 25 实测):

java Hi.java
# 输出:Hello from Java 25

与 JDK 8 对比#

public class App {
    public static void main(String[] args) {
        System.out.println("Hello, Java 25!");
    }
}
public class App {
    void main() {                 // 实例、无参、非 static
        System.out.println("Hello, Java 25!");
    }
}
void main() {                     // 连类声明都省了
    System.out.println("Hello, Java 25!");
}

底层原理#

入口放宽分两步落地:JDK 9 起 launcher 已能灵活解析 main(无显式 publicString[] 省略等),JEP 445(预览)最终在 JEP 512 转正

  • 实例 main:launcher 在启动时 new 一个无参实例,再调用其 void main();之前的 public static void main(String[]) 仍然有效(完全向后兼容)。
  • 紧凑源文件:单文件源码若没有显式类声明,编译器合成一个未命名类(unnamed class),把顶层 void main() 作为其入口。这仅限单文件直接启动java X.java),不能用在被打包的类/模块里。
graph LR
    S["源码 void main()"] --> C{"有类声明?"}
    C -- 无 --> U["合成未命名类(compact)"]
    C -- 有 --> N["具名类"]
    U --> L["launcher: new 实例 → main()"]
    N --> L
    style S fill:#bbdefb
    style L fill:#ffe0b2

常见坑 / 最佳实践#

  • 实例 main 需要无参构造:launcher 用 new ClassName() 建实例;若类没有可达的无参构造器,启动失败。
  • 紧凑源文件只能单文件启动java X.java 场景才生效;一旦放进包/模块、交给 javac/构建工具,就得正常写类声明。
  • 旧入口仍受支持public static void main(String[]) 一切照旧,迁移是渐进的,不必一刀切。
  • args 要用时:无参 main() 拿不到命令行参数;需要参数就用带 String[] args 的形式(仍可为实例方法)。

小结#

JEP 512 大幅降低 Java 的"启动税":实例 void main() 让入口更贴近面向对象直觉,紧凑源文件让单文件脚本几乎无样板。对教学、脚本、快速实验尤其友好。

参考#