实例 main 与紧凑源文件(JEP 512)#
版本信息
- 最低 JDK:25(21–24 预览 → 25 正式)
- JEP:JEP 512: Compact Source Files and Instance Main Methods
为什么需要#
JDK 8 的程序入口必须长这样:
public class App {
public static void main(String[] args) { /* ... */ }
}
对初学者和小脚本来说门槛偏高:要懂 public、static、String[] 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());
}
}
- :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(无显式 public、String[] 省略等),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() 让入口更贴近面向对象直觉,紧凑源文件让单文件脚本几乎无样板。对教学、脚本、快速实验尤其友好。