Java 23新特性实战:不用javac,直接java运行多文件程序

2026-08-18 0 127

以前给别人演示一个小工具,如果代码拆成几个类,就得先javac编译一通,再java运行主类。要是旁边没有装JDK,只有JRE,还得捣鼓classpath,麻烦得很。上周末翻Java 23的发布说明,发现JEP 458给了个爽快的玩法——你可以直接java Main.java运行一个包含多个类的程序,不再需要手动编译了。今天拿一个真实的小案例来试一试。

从单文件到多文件,解决了什么别扭

Java 11开始有“单文件源码启动”,也就是java Test.java直接跑。但如果你在Test.java里用了另一个自定义类,就算这个类也放在同一个目录里,直接运行还是会报“找不到符号”。所以以往还得退回去用javac。

JEP 458直接把“单文件”扩展成了“多文件”。只要用java命令启动一个入口源文件,它会自动在当前目录下寻找这个源文件引用的其他源文件,在内存里编译并运行。也就是说,你不用看到.class文件,不需要敲编译命令,源码就是可执行的东西。

一、先看一个完整的案例

我写了一个很简单的订单结算程序,里面有两个普通的类和一个record。功能是算2个商品的总价和折扣。

先建立三个文件,放在同一个目录,比如order文件夹下。

Item.java

public class Item {
    private final String name;
    private final double price;
    private final int quantity;

    public Item(String name, double price, int quantity) {
        this.name = name;
        this.price = price;
        this.quantity = quantity;
    }

    public double getTotal() {
        return price * quantity;
    }

    public String getName() {
        return name;
    }
}

Order.java

import java.util.List;

public class Order {
    private final List<Item> items;

    public Order(List<Item> items) {
        this.items = items;
    }

    public double calcTotal() {
        double total = 0;
        for (Item item : items) {
            total += item.getTotal();
        }
        return total;
    }

    public double calcDiscountedTotal(double discountRate) {
        return calcTotal() * (1 - discountRate);
    }
}

Main.java

import java.util.List;

public class Main {
    public static void main(String[] args) {
        var items = List.of(
            new Item("薯片", 8.5, 2),
            new Item("可乐", 3.0, 3)
        );

        var order = new Order(items);

        System.out.println("商品明细:");
        for (Item item : items) {
            System.out.printf("  %s x%d,小计%.2f%n", item.getName(), item.getQuantity(), item.getTotal());
        }

        System.out.printf("原价合计:%.2f%n", order.calcTotal());
        System.out.printf("打八折后:%.2f%n", order.calcDiscountedTotal(0.2));
    }
}

运行命令,假设命令行当前目录在order下:

java Main.java

直接得到输出:

商品明细:
  薯片 x2,小计17.00
  可乐 x3,小计9.00
原价合计:26.00
打八折后:20.80

全程没有javac,也没有生成class文件。这比我预想的还爽,因为它连入口类里import的那些类也自动找到了。

二、它是怎么找到的吗

JEP 458做的事情本质上是“用javac的源码路径机制”自动解析源码。当你执行java Main.java,编译器会从Main.java开始,把其中引用的类型对应的源文件也加载进来,相当于在内存里做一次增量编译。只要这些类没有循环依赖或者缺失,就可以直接运行。即使你用了Record、枚举、内部类,都没问题。

如果你用的是Java 21或更早版本,这个功能是没有的。我当时用Java 17试了下,java Main.java会报错找不到Item。升级到Java 23后,一切都安静了。

三、带包的代码也支持

如果你的源文件声明了package,只要目录结构和包名一致,照样能跑。只是你需要把入口文件的路径带上去,比如:

java demo/order/Main.java

假设Main.java位于demo/order/下,并且包含package demo.order;,那么它引用的其他类会自动从包路径下搜到。实际上JEP 458规定启动的源文件不能是被模块化的代码,也就是说不要在源文件旁边放一个module-info.java。但用于普通的小项目足够了。

四、能不能用第三方JAR包

可以,通过–class-path选项。比如项目里用了Apache Commons Lang的StringUtils,启动命令就是:

java --class-path commons-lang3.jar Main.java

但要注意:这个模式下,class-path里的jar包只能提供外部依赖,不能替代你当前要编译的源码。你的源码仍然必须能被find出来。

五、编写代码时最好注意的几个点

  • 所有源文件必须放在一个能被自动搜索到的目录里。最方便的就是把入口文件和其他类放在同一个顶层目录下。如果你的包结构比较复杂,只要目录按包名放好,也能自动找到。
  • 不要混用不同包的默认构造。如果多个类在同一个包里,互相可见没问题。但如果你写了一个带package的类,又写了一个无package的类,很容易导致找不到。建议要么全带包,要么全不带。
  • 启动的源文件不能有module-info.java。也就是不支持模块化。所以那些需要用到Java模块路径的项目还得按传统方式。
  • 每次运行会有编译开销。因为它是“现场编译”,每次跑都会重新在内存里编译一次。代码量小还好,要是几千个类,启动速度会比较感人。所以适合小工具、脚本化教学、快速Demo,不适合大型应用。

六、再展示一个更贴近“脚本”的玩法

为了体现这个特性的便利,我写一个基于Record和Sealed Interface的小程序,模拟一个“形状周长计算”。这种Java新特性和多文件启动搭配起来,代码就像脚本一样快。

创建shape目录,下面是Shape.java

public sealed interface Shape permits Circle, Rectangle {
    double perimeter();
}

Circle.java

public record Circle(double radius) implements Shape {
    @Override
    public double perimeter() {
        return 2 * Math.PI * radius;
    }
}

Rectangle.java

public record Rectangle(double width, double height) implements Shape {
    @Override
    public double perimeter() {
        return 2 * (width + height);
    }
}

Main.java

public class Main {
    public static void main(String[] args) {
        Shape circle = new Circle(3);
        Shape rectangle = new Rectangle(4, 5);

        System.out.printf("半径3的圆周长:%.2f%n", circle.perimeter());
        System.out.printf("4x5的矩形周长:%.2f%n", rectangle.perimeter());
    }
}

然后在shape目录下执行:

java Main.java

结果就像运行脚本那样,打印出周长。同时,Record的构造方法和toString也自动生成,整个程序结构非常紧凑。

七、什么时候我会用它

最直接的是给同事演示某个新API或算法时,不用先开IDE,也不用复制粘贴成一个类。直接传一个目录过去,他们自己用java Main.java就能跑起来。

另外在做代码教学的时候也很方便,因为学生不用理解编译和类路径,先专注在业务逻辑上。等后来真正需要大型项目时,再讲构建工具。

如果你喜欢写一些“命令行小工具”放在本机,这个方法也特别舒服——写完直接跑,还不会在目录里留下.class垃圾。

八、与脚本语言相比,Java终于有了点“脚本感”

Java确实不是脚本语言,但这个特性让它更轻快了。你依然需要写public static void main,但你已经不用管编译这一步了。对于Java新手来说,这是一个非常友好的入门方式:先用普通文本编辑器写几个类,然后一条命令运行。没有Maven,没有Gradle,没有classpath的迷宫。

不过也别指望能完全脱离javac。多文件源码启动在背后仍然调用jdk内部的编译器,只是帮我们省略了显式的编译命令和中间产物。它更像是一个“隐式编译”的入口。

九、总结

JEP 458让Java向“方便”又迈了一步。这次不用写任何构建脚本,也没有外部依赖,直接就有一个纯JDK的方案来运行多文件程序。对快速原型、算法演练、小工具来说,几乎完美。如果你还在使用Java 17或21,不妨升级到Java 23体验一下。以后我写demo再也不想开IDE了,直接一个vscode加终端就够了。

如果你也经常要给人分享代码片段,试试这个新玩法,确实能省下不少时间。

Java 23新特性实战:不用javac,直接java运行多文件程序
收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

版权声明:
本站资源有的来自互联网收集整理,本站纯免费分享提供学习使用,如果侵犯了您的合法权益,请联系本站我们会及时删除。
本站资源仅供研究、学习交流之用,免费开源项目不代表完全可商用,若商业用途请先咨询开发企业能否商用,否则产生的一切后果将由下载用户自行承担。
原创板块未经允许不得转载,否则将追究法律责任。

淘吗网 java Java 23新特性实战:不用javac,直接java运行多文件程序 https://www.taomawang.com/server/java/2563.html

常见问题

相关文章

猜你喜欢
发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务