鹏磊我还记得第一次写 Java 程序时的经历:必须声明一个类,必须写 public static void main(String[] args),即使只是打印一行 "Hello World"。这些样板代码对初学者来说是不小的负担。
JEP 512 彻底改变了这个现状。通过紧凑源文件和实例主方法,你可以像写 Python 或 JavaScript 脚本一样写 Java 代码:不需要显式声明类,main 方法可以是实例方法,还新增了 java.lang.IO 类简化控制台 I/O。鹏磊我觉得这个特性不仅降低了 Java 的学习门槛,也让快速编写工具脚本变得更加便捷。
紧凑源文件是啥
紧凑源文件(Compact Source Files)就是允许你在源文件里直接写方法和字段,不用非得包在一个类里。编译器会自动给你生成一个隐式的顶层类,把这些代码都装进去。
以前写 Java 程序,必须得这样:
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
现在可以这样写:
void main() {
IO.println("Hello, World!");
}
就这么简单,类都不用声明了,编译器会自动处理。这对新手来说太友好了,不用一开始就被类、static 这些概念搞懵。
实例主方法
实例主方法(Instance Main Methods)就是允许 main 方法不是 static 的,可以是实例方法。Java 启动器会自动实例化类,然后调用这个实例方法。
以前 main 方法必须是这样的:
public static void main(String[] args) {
// 代码
}
现在可以这样:
void main() {
// 代码,不用 static 了
}
// 或者带参数
void main(String[] args) {
// 代码,参数是可选的
}
这样写的好处是,你可以在 main 方法里直接访问实例字段和方法,不用再搞一堆 static 的东西了。
java.lang.IO 类
JEP 512 还引入了 java.lang.IO 类,专门用来简化控制台的输入输出。这个类提供了几个静态方法:
IO.print()- 打印内容,不换行IO.println()- 打印内容并换行IO.readln()- 读取一行输入
而且这个类是隐式导入的,你不用写 import 就能直接用。
以前得这样写:
System.out.println("Hello"); // 输出
Scanner scanner = new Scanner(System.in); // 输入得搞个 Scanner
String line = scanner.nextLine();
现在直接这样:
IO.println("Hello"); // 输出,简单多了
String line = IO.readln(); // 输入,一行搞定
完整示例
咱们看几个完整的例子,感受一下这个特性的威力。
最简单的 Hello World
// 最简化的 Hello World,啥都不用,直接写
void main() {
IO.println("Hello, World!"); // 直接用 IO.println,不用 System.out
}
就这么几行,以前得写个类、写个 static main,现在直接写逻辑就行了。
带参数的程序
// 处理命令行参数,main 方法可以是实例方法
void main(String[] args) {
if (args.length == 0) {
IO.println("请提供参数"); // 没参数就提示
} else {
IO.println("参数个数: " + args.length); // 打印参数个数
for (String arg : args) {
IO.println("参数: " + arg); // 遍历打印每个参数
}
}
}
你看,main 方法可以是实例方法,参数还是可以有的,用起来和以前差不多,但是不用 static 了。
使用实例字段和方法
因为 main 是实例方法了,所以可以直接用实例字段和方法:
// 定义实例字段
String greeting = "你好"; // 实例字段,不用 static
// 实例方法
void sayHello(String name) {
IO.println(greeting + ", " + name + "!"); // 用实例字段和方法
}
// 主方法,也是实例方法
void main() {
sayHello("世界"); // 直接调用实例方法,不用搞 static
greeting = "Hello"; // 直接修改实例字段
sayHello("World"); // 再调用一次
}
这样写代码更自然,不用被 static 的概念束缚,特别是对新手来说,理解起来更容易。
使用自动导入的类
紧凑源文件会自动导入 java.base 模块的所有公共顶层类和接口,所以常用的类不用 import 就能用:
// 直接用 java.util 的类,不用 import
void main() {
List<String> list = new ArrayList<>(); // List 和 ArrayList 直接能用
list.add("Java");
list.add("Python");
list.add("Go");
Map<String, Integer> map = new HashMap<>(); // Map 和 HashMap 也能直接用
map.put("Java", 1);
map.put("Python", 2);
IO.println("列表: " + list); // 打印列表
IO.println("映射: " + map); // 打印映射
// 用 Stream API,也不用 import
list.stream()
.filter(s -> s.length() > 3) // 过滤长度大于 3 的
.forEach(IO::println); // 打印结果
}
你看,java.util、java.util.stream 这些包里的类都能直接用,不用写一堆 import 了。
控制台输入输出
用 IO 类做输入输出特别简单:
// 简单的交互程序
void main() {
IO.print("请输入你的名字: "); // 提示输入,不换行
String name = IO.readln(); // 读取一行输入
IO.print("请输入你的年龄: ");
String ageStr = IO.readln();
int age = Integer.parseInt(ageStr); // 解析成整数
IO.println("你好, " + name + "! 你今年 " + age + " 岁"); // 输出结果
if (age >= 18) {
IO.println("你已经成年了"); // 判断并输出
} else {
IO.println("你还是个孩子"); // 否则输出这个
}
}
这样写交互程序简单多了,不用搞 Scanner、不用搞 System.in,直接用 IO.readln() 就完事了。
实际应用场景
这个特性在实际开发中还是挺有用的,特别是下面这些场景:
场景一:快速脚本和工具
写个简单的工具脚本,不用搞一堆样板代码了:
// 简单的文件处理脚本
void main(String[] args) {
if (args.length < 2) {
IO.println("用法: java Script.java <输入文件> <输出文件>"); // 提示用法
return; // 参数不够就退出
}
String inputFile = args[0]; // 输入文件名
String outputFile = args[1]; // 输出文件名
try {
// 读取文件,java.io 的类也能直接用
String content = Files.readString(Path.of(inputFile)); // 读取整个文件
String processed = content.toUpperCase(); // 转成大写
Files.writeString(Path.of(outputFile), processed); // 写入文件
IO.println("处理完成"); // 提示完成
} catch (Exception e) {
IO.println("错误: " + e.getMessage()); // 出错就打印错误信息
}
}
这种小脚本用紧凑源文件写特别合适,不用搞类结构,直接写逻辑就行。
场景二:学习和教学
教新手 Java 的时候,可以先用紧凑源文件让他们理解基本概念,不用一开始就被类、static 这些概念搞懵:
// 教循环和条件判断,不用先讲类
void main() {
IO.println("猜数字游戏"); // 游戏说明
IO.println("我想了一个 1 到 100 之间的数字");
int target = (int)(Math.random() * 100) + 1; // 生成随机数
int guess;
int attempts = 0; // 尝试次数
do {
IO.print("请猜一个数字: ");
String input = IO.readln(); // 读取输入
guess = Integer.parseInt(input); // 转成整数
attempts++; // 增加尝试次数
if (guess < target) {
IO.println("太小了"); // 提示太小
} else if (guess > target) {
IO.println("太大了"); // 提示太大
} else {
IO.println("恭喜你,猜对了!用了 " + attempts + " 次"); // 猜对了
}
} while (guess != target); // 循环直到猜对
}
这样的代码对新手来说更容易理解,先学会基本语法,再慢慢理解面向对象的概念。
场景三:原型和实验
快速验证想法、写个原型,用紧凑源文件特别方便:
// 快速验证算法
void main() {
IO.println("快速排序示例"); // 标题
int[] arr = {64, 34, 25, 12, 22, 11, 90}; // 测试数组
IO.println("排序前: " + Arrays.toString(arr)); // 打印排序前
quickSort(arr, 0, arr.length - 1); // 调用快速排序
IO.println("排序后: " + Arrays.toString(arr)); // 打印排序后
}
// 快速排序实现
void quickSort(int[] arr, int low, int high) {
if (low < high) {
int pi = partition(arr, low, high); // 分区
quickSort(arr, low, pi - 1); // 递归排序左边
quickSort(arr, pi + 1, high); // 递归排序右边
}
}
// 分区函数
int partition(int[] arr, int low, int high) {
int pivot = arr[high]; // 选择最后一个元素作为基准
int i = low - 1; // 小于基准的元素的索引
for (int j = low; j < high; j++) {
if (arr[j] < pivot) { // 如果当前元素小于基准
i++; // 增加索引
swap(arr, i, j); // 交换元素
}
}
swap(arr, i + 1, high); // 把基准放到正确位置
return i + 1; // 返回基准的位置
}
// 交换函数
void swap(int[] arr, int i, int j) {
int temp = arr[i]; // 临时变量
arr[i] = arr[j]; // 交换
arr[j] = temp; // 完成交换
}
写算法验证的时候,不用搞类结构,直接写函数就行,特别方便。
注意事项和限制
虽然这个特性挺方便的,但是也有一些需要注意的地方:
1. 只能有一个顶层类
如果源文件里有显式的类声明,那就不能再用紧凑源文件的特性了。紧凑源文件和显式类声明不能混用。
// 这样不行,有显式类就不能用紧凑源文件
public class MyClass {
// ...
}
void main() { // 编译错误:不能在显式类外面写方法
// ...
}
2. 隐式类名
编译器生成的隐式类名是基于源文件名的。如果你的源文件叫 Hello.java,那隐式类名就是 Hello。
// 文件: Hello.java
void main() {
IO.println("Hello"); // 隐式类名是 Hello
}
运行的时候得用文件名:
java Hello.java # 直接运行源文件
3. IO 类的限制
IO 类主要是为了简化基本的输入输出,功能比较有限。如果需要更复杂的 I/O 操作,还是得用传统的 java.io 或 java.nio。
// IO 类适合简单场景
void main() {
IO.println("简单输出"); // 这个可以
String line = IO.readln(); // 读取一行,也可以
// 但是复杂的 I/O 操作还是得用传统方式
// 比如读取二进制文件、网络 I/O 等
}
4. 自动导入的范围
自动导入只包括 java.base 模块导出的公共顶层类和接口。如果要用其他模块的类,还是得显式导入。
// java.base 的类可以直接用
void main() {
List<String> list = new ArrayList<>(); // 可以,java.util 在 java.base 里
String str = "test"; // 可以,String 在 java.base 里
}
// 但是其他模块的类就不行了
// import java.sql.Connection; // 如果要用 java.sql,还是得 import
5. 与现有代码的兼容性
这个特性不会影响现有的代码。如果你有传统的 Java 代码,完全不受影响,可以继续用。
// 传统方式还是可以用的
public class Traditional {
public static void main(String[] args) {
System.out.println("传统方式"); // 这个还是能用的
}
}
与传统方式的对比
咱们对比一下传统方式和新的紧凑源文件方式:
传统方式
import java.util.*;
public class Example {
private String name; // 实例字段
public Example(String name) {
this.name = name; // 构造函数
}
public void sayHello() {
System.out.println("Hello, " + name); // 实例方法
}
public static void main(String[] args) {
Example example = new Example("World"); // 创建实例
example.sayHello(); // 调用方法
}
}
代码行数:15 行左右
复杂度:需要理解类、构造函数、static 等概念
紧凑源文件方式
String name = "World"; // 直接定义字段
void sayHello() {
IO.println("Hello, " + name); // 直接定义方法
}
void main() {
sayHello(); // 直接调用,不用创建实例
}
代码行数:7 行左右
复杂度:更简单,不用理解那么多概念
你看,代码量减少了一半,而且更容易理解。
最佳实践
根据我的经验,用紧凑源文件的时候,建议这么搞:
1. 适合的场景
- 简单的脚本和工具
- 学习和教学示例
- 快速原型和实验
- 一次性任务
2. 不适合的场景
- 大型项目(还是用传统的类结构)
- 需要复杂继承和多态的场景
- 需要模块化的项目
- 生产环境的复杂应用
3. 代码组织建议
即使是紧凑源文件,也要注意代码组织:
// 字段定义
String config = "default";
int maxRetries = 3;
// 辅助方法
void log(String message) {
IO.println("[LOG] " + message); // 日志方法
}
boolean isValid(String input) {
return input != null && !input.isEmpty(); // 验证方法
}
// 主逻辑
void main(String[] args) {
log("程序启动"); // 记录启动
if (args.length == 0) {
log("没有参数"); // 记录信息
return;
}
for (String arg : args) {
if (isValid(arg)) { // 验证参数
log("处理: " + arg); // 处理参数
}
}
log("程序结束"); // 记录结束
}
虽然不用类,但是把字段、辅助方法、主逻辑分开,代码还是能保持清晰的。
总结
JEP 512 的紧凑源文件和实例主方法是个挺实用的特性,特别是对新手和写小脚本的场景。它让 Java 变得更简单、更易学,不用一开始就被类、static 这些概念搞懵。
不过这个特性也不是万能的,大型项目还是得用传统的类结构。它主要是为了降低学习门槛,让 Java 能像 Python、JavaScript 那样快速写小脚本。
总的来说,这个特性让 Java 变得更亲民了,特别是对那些刚入门的新手来说,能更快上手,不用被一堆样板代码吓到。如果你还没试过,建议试试,特别是写小工具的时候,用紧凑源文件能省不少事。
好了,今天就聊到这,有啥问题欢迎留言讨论。下次咱们聊聊 JEP 513,灵活构造函数体,也是个挺有意思的特性。