引言:从两步到一步

在 Java 11 之前,运行任何一个 Java 程序——哪怕是最简单的 “Hello World”——都必须经历一个固定的两步流程:

1
2
$ javac HelloWorld.java   # 第一步:编译生成 HelloWorld.class
$ java HelloWorld # 第二步:运行编译后的字节码

这种流程对于大型项目而言是严谨且必要的,但对于初学者练手、快速验证代码片段或编写小型工具脚本来说,却显得有些繁琐,且每次修改后都必须重新编译,否则运行的仍是旧版本的字节码。

从 Java 11 开始,这一流程得到了极大的简化。现在,你可以跳过显式的 javac 编译步骤,直接使用 java 命令运行源代码文件

功能初探:一行命令搞定一切

使用方法

新用法非常简单。假设你有一个 HelloWorld.java 文件,内容如下:

1
2
3
4
5
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello Java 11!");
}
}

在命令行中,直接输入以下命令即可运行:

1
$ java HelloWorld.java

终端将直接输出 Hello Java 11!

工作原理:内存中的“隐形”编译

这一功能是 Java 11 引入的 JEP 330: Launch Single-File Source-Code Programs 的核心内容。其原理是:当 java 启动器检测到目标是 .java 源文件时,它会在后台自动调用编译器(javac,将源代码编译为字节码。与传统编译不同的是,这些字节码文件(.class)并不会被写入磁盘,而是全部存储在内存中。编译完成后,JVM 会立即从内存中加载并执行这个类。

也就是说,java HelloWorld.java 在效果上近似于 javac -d <memory> HelloWorld.java && java -cp <memory> HelloWorld,整个过程对用户完全透明。

参数传递

向程序传递命令行参数的方式与传统方式完全一致。参数直接写在源文件名之后即可:

1
$ java HelloWorld.java Java 11

上面的代码中,args[0] 的值为 Javaargs[1] 的值为 11

宽松的文件名规则

一个有趣的点是,该功能对文件名与公共类名的匹配要求较为宽松。例如,将文件重命名为 WrongName.java(内容不变),依然可以直接运行:

1
$ java WrongName.java

程序会正常执行。但这仅限 java 命令;若改用 javac 编译该文件,则会因类名与文件名不符而报错。当然,尽管可以运行,始终建议遵循标准的命名规范(文件名与 public class 名一致),以保持代码的可维护性。

进阶选项:命令行与 Shebang 脚本

源文件模式的触发条件

java 命令进入“源文件模式”有两个触发条件,满足其一即可:

  1. 文件名后缀:命令行第一个参数(JVM 选项之后)是一个以 .java 结尾的文件名。
  2. --source 参数:命令行中显式指定了 --source 版本号。

使用 --class-path 选项

你仍然可以在运行源文件时指定类路径(--class-path-cp),选项需要放在源文件名之前:

1
$ java --class-path=/some/path HelloWorld.java

Shebang 支持:像脚本一样运行 Java

Java 11 还支持将 Java 程序作为 Shebang 脚本运行,进一步模糊了编译型语言与脚本语言之间的界限。在 Unix/Linux/macOS 系统上,你可以创建一个没有 .java 扩展名的文件,并在第一行添加 Shebang(#!)指令,指向 Java 启动器的路径并指定 --source 版本。

示例: 创建一个名为 add 的文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/env -S java --source 11

import java.util.Arrays;

public class Addition {
public static void main(String[] args) {
Integer sum = Arrays.stream(args)
.mapToInt(Integer::parseInt)
.sum();
System.out.println(sum);
}
}

赋予可执行权限后,即可像普通脚本一样调用:

1
2
3
$ chmod +x add
$ ./add 1 2 3
6

注意:Shebang 功能需要 --source 参数支持,且文件不能使用 .java 扩展名,否则 Shebang 行会被视为非法字符而导致错误。该功能在 Windows 上不直接支持。

限制与适用场景

这项功能虽便捷,但存在明确的限制,了解这些限制有助于避免踩坑。

核心限制:单文件

在 Java 11 中,此功能仅适用于单个源文件。程序所依赖的其他类必须定义在同一个 .java 文件中,不能引用其他源文件或第三方 JAR 包。例如,如果 Student.java 引用了 Teacher.java 中的 Teacher 类,运行时将报错“找不到符号”。

类路径冲突

如果当前目录下同时存在同名的源文件(如 HelloWorld.java)和编译后的字节码文件(如 HelloWorld.class),再使用 java HelloWorld.java 运行时会报错:error: class found on application class path。这是为了防止类路径冲突导致的行为不一致。

适用场景

  • Java 初学者练手:省去了编译步骤,降低入门门槛。
  • 快速原型验证与探索性编程:快速测试某个 API 或代码片段,无需建立完整项目。
  • 编写小型实用工具或脚本:在熟悉 Java 生态的情况下,替代部分 Shell/Python 脚本。
  • 不适合:大型项目、需要外部依赖(JAR 包)、涉及多模块的项目开发。

未来演进:多文件支持

Java 22 进一步增强了此功能。通过 JEP 458: Launch Multi-File Source-Code Programsjava 启动器现在可以自动定位、编译并加载多个源文件,前提是源文件的目录结构与其包结构相匹配。这意味着你可以在不借助构建工具的情况下,直接运行由多个源文件组成的简单程序,进一步拓宽了“免编译运行”的使用场景。

总结

Java 11 引入的“直接运行源文件”功能,是一项旨在提升开发体验的务实改进。它通过将编译步骤隐藏在内存中,将开发者从繁琐的两步操作中解放出来,尤其适合快速迭代和轻量级编程场景。不过,它并非要取代传统的 javac + java 流程,而是在特定场景下提供一个更轻便、更高效的替代方案。掌握这一特性,能让你的 Java 日常开发与学习更加游刃有余。