04、JDK 25 新特性:紧凑源文件和实例主方法(JEP 512)快速入门

鹏磊我还记得第一次写 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,灵活构造函数体,也是个挺有意思的特性。

本文章最后更新于 2025-11-27