java中的properties类-java类中调用方法
Java 的 main 方法声明终于要变天了吗? —— 浅谈 JEP 445
原文发布于我的微信公众号和博客:HikariLan's Blog。
前言
半天前,reddit 上一篇名为 “Oracle trying to troll us.”的帖子突然爆火,随后这张图片被传入中文互联网,同样引发了网友热烈的讨论。这篇帖子的文章内容只有这样一张图片:
如果你是一位苦逼的 Java 程序员,那么当你看到这张图的时候也许震惊的会跳起来,但是如果你没有看懂,那就且听我细细往下说......
JEP 445 的前世今生
JEP 445: Unnamed Classes and Instance Main Methods (Preview) 的标题翻译过来是 “未命名类和实例 main 方法”,仅看标题你可能并不认为和上面那些东西有什么关系,但事实上,上述特性确实是由此 JEP 带来的。
事实上,JEP 445 早在 2023 年 2 月就被创建了,单之所以刚刚才火,是因为 OpenJDK 14 个小时前才批准了这个 JEP 的代码实现:JDK-8306112 Implementation of JEP 445: Unnamed Classes and Instance Main Methods (Preview) by JimLaskey · Pull Request #13689 · openjdk/jdk (github.com)
值得一提的是,JEP 445 是一个即将在 Java 21 中引入的预览(preview)提案,这意味着你需要通过在编译和运行时传入 --release 21 和 --enable-preview 命令行参数才能体验到这个功能
这种简化写法并不是 Java 的特例,其实早在 .NET 6,C# 就引入了一套 "控制台模板" 语法,其允许你在 C# 的主类文件(这里是 Program.cs)这么写:
// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");
其等价于:
using System;
namespace MyApp // Note: actual namespace depends on the project name.
{
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}
很神奇对不对,但实际上说简单点这只是套语法糖而已。那么,JEP 445 也是如此吗?答案是否定的,甚至,它连语法糖都没有引入。
真的是变天了吗?
如果你仔细查看 JEP 提案的原文,你会发现他们在 Summary 和 Goal 上提到最多的两个词是:sutdents 和 beginners:
而仔细读读这部分内容你会知道,这个 JEP 设立的初衷是为了为学生和 Java 新手隐去晦涩难懂的部分,仅保留一些简单的语法,方便他们快速入门和学习 Java,但并不是引入了一套额外的 Java 方言。
从始至终,这套东西就不是给普通 Java 开发者使用的,而是面向学生和新手入门使用的。
那么,JEP 445 到底引入了一套什么样的机制呢?
未命名类和实例 main 方法
JEP 445 引入了如下两个机制:Unnamed Classes 和 Instance Main Methods,通过如下两个机制,简化了 main 方法的声明。让我们先从后者开始讲起。
实例 main 方法
首先,我们来看如下代码:
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
一个非常经典的“Hello World”代码,一个 HelloWorld.java 文件中包含了一个 HelloWorld 类,其中包含一个公开的静态 main 方法,并包含 args 形参;方法体内调用 System.out.println 方法打印 Hello, World! 到标准输出中。
这无疑非常复杂,以至于我用自然语言描述这段代码都用了三行字。而最烦人的是,这是 main 方法唯一的写法,你没有任何办法简化这套代码,哪怕像 C 语言那样隐去 args 形参都不行。
为了解决这个问题,JEP 445 引入了一套“灵活的启动协议(flexible launch protocol)”。首先,这允许“实例 main 方法”存在,所谓“实例 main 方法”,就是指“非静态的 main 方法”,这意味着,main 方法将可以是 non-static 的;接着一个 main 方法的访问修饰符将不必是 public 的,只需要是 non-private(也即public, protected 和 package-protected)的即可;最后,main 方法中的 String[] args 将是可选传入的。这意味着,你现在可以将代码简化到如下程度:
class HelloWorld {
void main() {
System.out.println("Hello, World!");
}
}
因此,上述代码从来就不是什么新的语法糖java中的properties类,而是我们所熟知的东西:一个 HelloWorld.java 文件中包含了一个 HelloWorld 类,其中包含一个包访问级别的非静态 main 方法,不包含形参;方法体内调用 System.out.println 方法打印 Hello, World! 到标准输出中。
非常有意思对不对,而如果存在多个 main 方法,将会以如下的优先级,选择优先级最高的一个 main 调用:
一个在启用类中声明,采用 non-private 访问级别的 static void main(String[] args) 方法;一个在启动类中声明,采用 non-private 访问级别的 static void main() 方法;一个在启动类中声明,或从其超类中继承的,采用 non-private 访问级别的 void main(String[] args) 方法;一个在启动类中声明,或从其超类中继承的,采用 non-private 访问级别的 void main() 函数。
这其实改变了 Java 原有的行为:如果一个启动类声明了一个非静态的 main 方法,同时其超类存在一个“传统的”public static void main(String[] args) 方法,那么现在 Java 将会调用前者,而不是后者(当然,如果你真的这么做了,JVM 会在运行时输出一个警告来提示你)。
最后,如果一个即将被调用的 main 方法是一个内部类的成员,那么程序将无法运行。
所以,JEP 445 事实上是通过一系列语法层面的让步引入了一套更加方便使用的 main 方法模板,而并不是引入了一套新的语法或是语法糖。
未命名类
也许你早已知道,当一个 Java 类文件位于源代码文件的顶级,也就是说其不属于任何包中时java中的properties类,那我们就说这个类属于一个“未命名包”。在 JEP 445 中,引入了“未命名类”的概念,当一个类源代码中不包含任何类声明,而仅有方法声明和成员变量声明时,该类便被称为“未命名类”。
未命名类永远是未命名包的成员,而且其永远是 final 的,也就是说其不能实现或拓展任何接口和类;未命名类无法使用静态方法的方法引用,但是仍然可以使用 this 关键字或非静态方法的方法引用。
未命名类不能被其他类按名称引用,也无法构造其实例;其内部写法与显式声明的类完全相同,除了其只能有一个默认的无参构造方法。
通过引入未命名类,上述代码最终可以被简化成这样:
void main() {
System.out.println("Hello, World!");
}
一个未命名类,其中包含一个包访问级别的非静态 main 方法,不包含形参;方法体内调用 System.out.println 方法打印 Hello, World! 到标准输出中。
除此之外,一个未命名类依然可以拥有成员变量和成员方法,例如这样:
String greetingMsg = "Hello, World!";
String greeting() { return greetingMsg; }
void main() {
System.out.println(greeting());
}
当 JVM 试图执行一个在一个未命名类中的非静态 main 方法时,实际上等同于创建了一个匿名类,然后再执行方法:
new Object() {
// the unnamed class's body
}.main();
我们可以通过 java 指令来直接运行一个未命名类源代码,像是这样:
$ java HelloWorld.java
然后,Java 编译器会将其编译为 HelloWorld.class,找到 main 方法并执行。即使这里我们给未命名类分配了一个名字,但是这个名字实际上是不能用在 Java 源代码中的。
最后,在当前预览版本中,如果我们的 Java 代码中含有未命名类,那么 javadoc 实用工具将无法生成 API 文档,因为其本身就无法被其他类访问。
后记
看完整个 JEP,我只想感叹 OpenJDK 开发者的脑洞确实是大,竟然通过引入两套新的机制,巧妙地解决了 Java main 方法冗长的问题,而并未引入新的语法或语法糖,以造成用户体验割裂。
这篇 Reddit 文章下的高赞评论给出了 JEP 445 的链接,随后提问到:“这将是 Java 模板代码梗的末日吗”,我想,至少在 JEP 445 中,这种痛苦还远未结束吧。(完)
引用