直接运行 Java 源文件 — JEP 330(JDK 11)
周五下午,飞翔科技的实习生小林抱着笔记本跑来找白歌。
"白哥,我写了个 30 行的工具类测试一下正则表达式,每次都要
javac编译再java运行,太麻烦了。有没有快一点的办法?"白歌伸手在她终端里敲了一行:
java RegexTest.java小林瞪大眼睛:"不需要 javac?!"
"JDK 11 的 JEP 330——" 白歌靠在椅背上,"单文件程序直接用
java XX.java就能跑。编译和执行一步到位,跟 Python 一样顺手。"小林试了一下,果然直接输出了结果:"这也太爽了,以后写脚本再也不建 Maven 项目了。"
定义表
| 概念 | 描述 |
|---|---|
| JEP 330 | JDK 11 引入的特性——允许直接使用 java 命令运行单个 .java 源文件 |
| 单文件源程序(Single-File Source-Code Program) | 无需显式编译,Java 启动器在内存中自动编译并执行单个源文件 |
| Shebang 支持 | Unix/Linux 系统上,.java 文件首行可写 #!/path/to/java --source 11,使 Java 源文件能像脚本一样直接执行 |
| 编译-运行两阶段(传统方式) | javac Foo.java → 生成 Foo.class → java Foo |
| 直接运行(JDK 11) | java Foo.java,编译在内存中完成,不产生 .class 文件(也可用 --source 指定版本) |
Mermaid 对比图:传统方式 vs 直接运行
基础用法
3.1 最简单的示例
// ===== HelloJEP330.java =====
public class HelloJEP330 {
public static void main(String[] args) {
System.out.println("飞翔科技 — JDK 11 直接运行 Java 源文件!");
// 可以 import 任何 JDK 自带的类
var now = java.time.LocalDateTime.now();
System.out.println("当前时间: " + now);
// 可以使用 Stream API
java.util.stream.Stream.of("Java", "Python", "Go")
.map(String::toUpperCase)
.forEach(System.out::println);
}
}
# 一行运行
java HelloJEP330.java
# 或显式指定 source 版本
java --source 11 HelloJEP330.java
输出:
飞翔科技 — JDK 11 直接运行 Java 源文件!
当前时间: 2026-06-14T16:30:00
JAVA
PYTHON
GO
3.2 多类定义(在同一 .java 文件中)
// ===== MultiClassDemo.java =====
// 可以有多个类,但文件名不必与 public class 名一致
class Employee {
String name;
int age;
Employee(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return name + " (" + age + " 岁)";
}
}
// 若有 public class,文件名需与其一致;单文件模式下不强制
public class MultiClassDemo {
public static void main(String[] args) {
var employees = java.util.List.of(
new Employee("孔蓝", 28),
new Employee("黄俪", 26),
new Employee("小崔", 27)
);
employees.forEach(System.out::println);
}
}
输出:
孔蓝 (28 岁)
黄俪 (26 岁)
小崔 (27 岁)
3.3 使用第三方类库(classpath 支持)
# --class-path 或 -cp 仍然可用
java -cp "lib/gson-2.10.1.jar" JsonTest.java
// ===== JsonTest.java =====
import com.google.gson.Gson;
public class JsonTest {
public static void main(String[] args) {
var gson = new Gson();
var data = java.util.Map.of("name", "飞翔科技", "year", 2026);
System.out.println(gson.toJson(data));
}
}
# 运行
java --class-path lib/gson-2.10.1.jar JsonTest.java
Shebang 风格(Unix / Linux)
在 Unix / Linux / macOS 上,可以把 Java 源文件作为可执行脚本:
#!/usr/bin/java --source 11
public class ShebangDemo {
public static void main(String[] args) {
System.out.println("第一行 shebang 会被编译器自动忽略");
System.out.println("参数个数: " + args.length);
for (int i = 0; i < args.length; i++) {
System.out.printf("参数[%d]: %s%n", i, args[i]);
}
}
}
# 赋予执行权限
chmod +x ShebangDemo.java
# 直接像脚本一样执行
./ShebangDemo.java 飞翔 科技 2026
输出:
第一行 shebang 会被编译器自动忽略
参数个数: 3
参数[0]: 飞翔
参数[1]: 科技
参数[2]: 2026
Windows 上不适用 Shebang,直接用
java XX.java即可。
适用场景与限制
5.1 适用场景
| 场景 | 说明 |
|---|---|
| 快速原型 / 脚本 | 临时数据处理、正则测试、算法验证——无需建工程 |
| 教学演示 | 讲师在课堂上写一个 .java 文件直接跑,无需带学生配 Maven |
| CI / 运维脚本 | Jenkins / 定时任务里可以直接跑 Java 源文件 |
| 代码小片段分享 | 一个文件包含完整逻辑,发给同事直接 java 跑 |
5.2 限制
| 限制 | 说明 |
|---|---|
| 仅限单文件 | 不能跨多个 .java 源文件;如果引用了同目录的其他类,需一起列在 --source-path 上 |
| 不能有 package 声明 | 带 package com.xxx; 的文件不能用 java XX.java 直接运行(除非用 --source-path 模拟) |
| 不能使用模块化 | 带 module-info.java 的项目必须走 javac + java 传统方式 |
| 不生成 .class | 编译在内存中完成,不落盘。如需持久化 class 文件,仍需 javac |
| 性能 | 每次执行都重新编译(虽然是增量且很快),高频调用场景不如预编译 |
与模块系统的交互
# 直接运行的源文件属于 unnamed module,不能 requires 命名模块
# 但可以通过 --add-modules 引入 JDK 模块
java --add-modules java.sql ScriptUsingJDBC.java
总结
| 维度 | 传统 javac + java | java XX.java(JEP 330) |
|---|---|---|
| 步骤 | 两步(编译 → 运行) | 一步(编译+运行) |
| 产物 | 生成 .class 文件 | 内存编译,不产文件 |
| 多文件项目 | 支持 | 仅单文件 |
| Shebang | 不支持 | 支持(Unix) |
| 适用场景 | 生产项目 | 脚本、原型、教学 |
| 第三方依赖 | -cp 引入 | --class-path 引入(一致) |