QUICKREF · LANGUAGE · JEP 512
Compact Source Files:没有 class 外壳的程序
JDK 25 · 速查主题 03 / 06 · 预计 25 分钟
一句话JDK 25 正式允许单文件程序省略 package 与 class 声明,直接写 import、字段、方法和实例 main。
java Hello.java 即可运行;它降低的是入门与小工具的门槛,不是替代正规类结构。学完你要能自己回答
- 紧凑源文件允许哪些顶层成员?隐式类编译后是什么样子?
java Hello.java与javac Hello.java的行为有何不同?类名从哪来?- 紧凑源文件的硬限制是什么?IO.println 为什么不能直接写成 println?
最小程序:没有 package,也没有 class01
JDK 25 的紧凑源文件可以只写一个实例 main 方法。编译器把未包裹的成员放进一个隐式类;本机 javac 以文件名生成类名(规范说生成名实现相关)。用 java Hello.java 启动时,源码在内存中编译并直接运行,不会留下 class 文件。
void main() {
IO.println("hello, jdk 25");
}
java Hello.java
hello, jdk 25
IO 在 JDK 25 位于 java.lang,因此不需要 import;但它的静态方法不再像 24 预览那样隐式导入,必须写 IO.println,或者显式 import static java.lang.IO.*;。
紧凑源文件在 21–24 是预览(早期叫 simple source files),需要 --enable-preview;25 正式,不再需要预览参数。
实例 main 与顶层成员02
紧凑源文件可以有顶层字段、方法和额外的顶层类。main 可以是实例方法,也可以带 String[] args;启动器会选择合适的主方法。字段和方法默认是隐式类的成员,而不是静态导入。
String greeting = "hello";
void main() {
IO.println(greeting + " from compact source, twice(21)=" + twice(21));
}
int twice(int x) {
return x * 2;
}
java CompactHello.java
hello from compact source, twice(21)=42
main可以写void main()、void main(String[] args),也可以是static。- 顶层字段/方法可以是实例的;隐式类会自动获得无参构造器。
- 文件里可以再声明普通顶层类,作为辅助类型。
- 参数通过
String[] args传入;java ArgsApp.java one two中 args.length 为 2。
紧凑源文件不是另一种语言方言。它只是省略了外层类声明;需要包、可见性、多个文件或测试时,应该展开成普通类。
编译后是什么样03
javac 仍然可以编译紧凑源文件。本机 javac 生成的隐式类名取自文件名,声明为 final 且包私有;成员也是包私有。规范只保证生成名是实现相关的,源码不应依赖这个类名。下面的 javap 输出来自本机编译结果。
javac --release 25 -d out CompactHello.java
javap -p out/CompactHello.class
Compiled from "CompactHello.java"
final class CompactHello {
java.lang.String greeting;
CompactHello();
void main();
int twice(int);
}
编译出的类也可以用 java -cp out CompactHello 启动;但紧凑源文件的主要入口仍是源码启动器。源码启动适合单文件工具,编译产物适合继续参与普通 classpath 流程。
隐式类不是公开 API 类型。不要把它当作库的入口,也不要在其他源文件里依赖它的名字。
限制:package、IO 静态导入与单文件04
紧凑源文件不能有 package 声明;这是它与普通源文件最硬的差别。下面的两个错误都在本机 JDK 25 上实际编译得到。
package demo;
void main() {
IO.println("package?");
}
CompactPackage.java:1: error: compact source file should not have package declaration package demo; ^ 1 error
void main() {
println("no implicit IO static import");
}
CompactPrintln.java:2: error: cannot find symbol
println("no implicit IO static import");
^
symbol: method println(String)
location: class CompactPrintln
1 error
- 不能写 package;需要包名时改用普通源文件。
IO的静态方法不再隐式导入;写IO.println或import static java.lang.IO.*;。- 一次启动一个源文件;它不是多文件应用的构建方式。
- 不能声明 module-info;模块导入可以用,但模块声明仍要单独文件。
- 隐式类是 final、包私有;不适合作为跨包 API。
java Hello.java 不接受 --release 之类的编译选项;需要固定目标版本时,先用 javac --release 25 编译,再运行 class。什么时候用,什么时候不用05
| 场景 | 适合紧凑源文件 | 原因 |
|---|---|---|
| 教学、演示、单文件脚本 | 适合 | 没有类外壳,入口清楚 |
| 命令行小工具 | 适合 | 一个文件即可分发和运行 |
| 库 / 框架 / 多文件应用 | 不适合 | 需要包、可见性、测试与构建 |
| 需要固定 --release 的构建 | 不适合 | 源码启动器不接受编译选项 |
| 需要公开 API 类型 | 不适合 | 隐式类包私有且 final |
- 先写紧凑源文件验证想法,再展开成普通类;展开时补 package、public class 和显式 main。
- 不要把业务逻辑堆在隐式类里;超过一个文件就应该进入正常工程结构。
- 团队规范里明确「单文件工具可以用」,避免和正式源码混在一起。
JEP 512 的目标是降低入门与小工具的门槛,不是让所有 Java 程序都变成脚本。正规项目仍然需要类、包和模块边界。
版本边界BOUNDARY
这张表比 API 本身更值得记。写代码时先确认你的运行时版本。
| 特性 / API | 状态 | 说明 |
|---|---|---|
| 紧凑源文件 | 21–24 预览 / 25 正式 | JEP 512;25 起无需 --enable-preview。 |
| 实例 main | 25 正式 | 支持 void main() / void main(String[] args)。 |
| `java.lang.IO` | 25 新增 | println / print / readln。 |
| IO 静态方法隐式导入 | 25 已取消 | 必须写 IO.println 或显式静态导入。 |
| package 声明 | 紧凑源文件不允许 | 编译错误:compact source file should not have package declaration。 |
| 隐式类 | final、包私有 | 本机 javac 以文件名生成类名;规范说生成名实现相关,不要依赖。成员包私有。 |
费曼自检SELF CHECK
先合上页面,用自己的话回答,再展开对照。能讲出因果和反例,才算真正掌握。
01. 紧凑源文件的 main 可以怎么写?
可以写实例或静态的 void main(),也可以带 String[] args;启动器会选择合适的主方法。隐式类会自动获得无参构造器。
02. java Hello.java 与 javac Hello.java 的关键差别是什么?
前者在内存中编译并直接运行,不产生 class 文件,也不接受 --release 等编译选项;后者生成以文件名命名的 class,可继续用 classpath 运行。
03. 为什么紧凑源文件里不能直接写 println(...)?
JDK 25 起 IO 的静态方法不再隐式导入到紧凑源文件;必须写 IO.println(...),或显式 import static java.lang.IO.*。
04. 紧凑源文件有哪些硬限制?
不能有 package 声明;一次启动一个源文件;不能声明 module-info;隐式类 final 且包私有,不适合作为公开 API。需要这些能力时展开成普通类。
学习进度
记录保存在本机浏览器 · 不上传