[Java] JDK 25 新特性之简化的 main 方法

364 阅读6分钟

JDK 25 新特性之简化的 main 方法

背景

JDK 25 发布了,其中包含了一些新特性,具体的变化可以参考 JDK 25 Release Notes。其中有一个变化是 main 方法可以用更简洁的方式来写了 ⬇️

void main() {
  IO.println("Hello, World!");
}

但这个特性是如何实现的呢?本文对此进行探讨。

要点

1. 对 main 方法的限制的变化

对作为应用入口的 main 方法的要求 ⬇️

JDK 版本允许的 access返回值入参是否 static
JDK 24必须是 publicvoid入参是 String 的数组(String[]或者String... 都可以)必须是 static
JDK 25public/protected/package access 都可以void没有入参 或者入参是 String 的数组(String[]或者String... 都可以)是或 都可以

有变化的地方,我用粗体标出来了 ⬆️

2. java.lang.IO

JDK 25 新增了 java.lang.IO 类,其中有静态方法 println(Object)

正文

JEP 512: Compact Source Files and Instance Main Methodsmain 方法的变化有详细的介绍,读者朋友如果有兴趣的话,可以看一看。

我们写点代码来看看到底发生了什么 ⬇️ (请将以下代码保存为 SimpleMain.java,其实具体的名称并不重要,不过为了方便下文的描述,我们还是用一个统一的名字)

// This is a complete and runnable program!
void main() {
  IO.println("Hello, World!");
}

用如下的命令可以编译 SimpleMain.java ⬇️

javac SimpleMain.java

编译之后,会生成 SimpleMain.class 文件。用以下命令可以查看 SimpleMain.class 的内容

javap -v -p SimpleMain

生成的结果如下 ⬇️ (最开头的 4 行已略去)

final class SimpleMain
  minor version: 0
  major version: 69
  flags: (0x0030) ACC_FINAL, ACC_SUPER
  this_class: #15                         // SimpleMain
  super_class: #2                         // java/lang/Object
  interfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool:
   #1 = Methodref          #2.#3          // java/lang/Object."<init>":()V
   #2 = Class              #4             // java/lang/Object
   #3 = NameAndType        #5:#6          // "<init>":()V
   #4 = Utf8               java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = String             #8             // Hello, World!
   #8 = Utf8               Hello, World!
   #9 = Methodref          #10.#11        // java/lang/IO.println:(Ljava/lang/Object;)V
  #10 = Class              #12            // java/lang/IO
  #11 = NameAndType        #13:#14        // println:(Ljava/lang/Object;)V
  #12 = Utf8               java/lang/IO
  #13 = Utf8               println
  #14 = Utf8               (Ljava/lang/Object;)V
  #15 = Class              #16            // SimpleMain
  #16 = Utf8               SimpleMain
  #17 = Utf8               Code
  #18 = Utf8               LineNumberTable
  #19 = Utf8               main
  #20 = Utf8               SourceFile
  #21 = Utf8               SimpleMain.java
{
  SimpleMain();
    descriptor: ()V
    flags: (0x0000)
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 2: 0

  void main();
    descriptor: ()V
    flags: (0x0000)
    Code:
      stack=1, locals=1, args_size=1
         0: ldc           #7                  // String Hello, World!
         2: invokestatic  #9                  // Method java/lang/IO.println:(Ljava/lang/Object;)V
         5: return
      LineNumberTable:
        line 3: 0
        line 4: 5
}
SourceFile: "SimpleMain.java"

由于结果并不复杂,我们可以手动写出对应的 java 代码 ⬇️

// 以下代码是我手动转化的,不保证绝对准确,仅供参考

final class SimpleMain {
  SimpleMain() {
  }
  
  void main() {
    IO.println("Hello, World!");
  }
}

class 文件以及手动转化出的 java 文件可以看到

  • main 方法的变化
    • 这里的 main 方法 不是 public static final
    • 这里的 main 方法 没有 String[] 类型的入参
  • main 方法里调用了 java.lang.IOprintln(...) 方法。

我们来看看这是如何做到的。

1. main 方法的变化

1.1 调用 main 方法

以前我们写的都是以下 2main 方法

  • public static void main(String[] args) 或者
  • public static void main(String... args)

为何现在可以打破这些限制呢?

简单来说,就是 Java Language Specification 做了相关的调整。 在 JDK 24相关描述 中,可以看到 ⬇️

image.png

所以截止到 JDK 24 时,作为程序入口的 main 方法还必须是以下两种形式之一

  • public static void main(String[] args) 或者
  • public static void main(String... args)

而在 JDK 25 中,同样的 章节 里,描述调整成这样了 ⬇️

image.png

这一小节的完整内容有点长,我把绿色框里的内容复制到下方

main method declared in, or inherited by, a given class is a candidate main method if either:

  • It has a single formal parameter (§8.4.1) whose declared type is an array of String, a void result, and publicprotected or package access; or
  • It has no formal parameters, a void result, and publicprotected or package access.

简单点说,就是原先那样的 public static void main(String[])/public static void main(String...) 方法继续有效,但现在对 main 方法的限制更宽松了(精确的描述请参考原文)⬇️

允许的 access返回值入参是否 static
public/protected/package access 都可以void没有入参 或者入参是 String 的数组(String[]或者String... 都可以)是或 都可以
1.2 main 方法在哪个类里?

虽然对 main 方法的限制变宽松了,但它还是存在于一个类中,这个类(即 SimpleMain)是怎么出现的呢?

JEP 512: Compact Source Files and Instance Main Methods 里提到

image.png

大意是说,如果 java 编译器遇到了不在任何类之内的字段/方法,则编译器会认为有隐式声明的类,来包含这样的字段/方法。

所以 SimpleMain 这个类是编译器检测到 void main() 方法后自动生成的类。

按照 The Java® Language Specification 中的 12.1.4. Invoke a main Method 小节 的描述,如果 main 方法是实例方法的话,在调用这个 main 方法前,会先创建对应类的实例 ⬇️

image.png

我们可以写点代码验证一下 ⬇️

非静态 main 方法需要在实例创建后才会被调用

请将以下代码保存为 SimpleMain2.java ⬇️

public class SimpleMain2 {
  
  public SimpleMain2() {
    IO.println("Hi, the constructor of SimpleMain2 is called");
  }

  void main(String[] arg) {
    IO.println("Hello, World!");
  }
}

以下命令可以编译 SimpleMain2.java 并运行其中的 main 方法。

javac SimpleMain2.java
java SimpleMain2

运行结果如下

Hi, the constructor of SimpleMain2 is called
Hello, World!

从运行结果中可以看到,的确是先创建了 SimpleMain2 的实例,然后再调用对应的 main 方法。

2. java.lang.IOprintln(...) 方法

JDK 25java.lang package 中增加了 IO.java,而其中有静态方法 println(Object) ⬇️ (其实它就是直接调用了 System.out.println(Object) 方法)

    /**
     * Writes a string representation of the specified object and then writes
     * a line separator to the standard output.
     *
     * <p> The effect is as if {@link java.io.PrintStream#println(Object) println(obj)}
     * had been called on {@code System.out}.
     *
     * @param obj the object to print, may be {@code null}
     */
    public static void println(Object obj) {
        System.out.println(obj);
    }

根据 The Java® Language Specification 中的 Chapter 7. Packages and Modules 中的描述(在下方)可知,java.lang 这个 package 里所有 publicclassinterface 都会被自动 importjava.lang.IO 这个新增的 classpublic 的,所以我们不必显式写出 import java.lang.IO; 这样的语句。

image.png

Code in a compilation unit automatically has access to all classes and interfaces declared in its package and also automatically imports all of the public classes and interfaces declared in the predefined package java.lang.

参考资料