半天前,reddit 上一篇名为 “Oracle trying to troll us.”的帖子突然爆火,随后这张图片被传入中文互联网,同样引发了网友热烈的讨论。这篇帖子的文章内容只有这样一张图片:

如果你是一位苦逼的 Java 程序员,那么当你看到这张图的时候也许震惊的会跳起来,但是如果你没有看懂,那就且听我细细往下说......
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 上提到最多的两个词是:students 和 beginners:

而仔细读读这部分内容你会知道,这个 JEP 设立的初衷是为了为学生和 Java 新手隐去晦涩难懂的部分,仅保留一些简单的语法,方便他们快速入门和学习 Java ,但并不是引入了一套额外的 Java 方言。
从始至终,这套东西就不是给普通 Java 开发者使用的,而是面向学生和新手入门使用的。
那么,JEP 445 到底引入了一套什么样的机制呢?
JEP 445 引入了如下两个机制:Unnamed Classes 和 Instance Main Methods,通过如下两个机制,简化了 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!");
}
}
因此,上述代码从来就不是什么新的语法糖,而是我们所熟知的东西:一个 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 类文件位于源代码文件的顶级,也就是说其不属于任何包中时,那我们就说这个类属于一个“未命名包”。在 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 中,这种痛苦还远未结束吧。(完)

1
Aloento 2023-06-05 01:56:02 +08:00
可以,支持
|
2
noreplay 2023-06-05 07:41:01 +08:00 via Android
也就是说当这个新特性铺开之后,Java 只会在自学刚开始爽,做工程还是那么便秘吗?
|
3
wangxiaodong 2023-06-05 08:21:11 +08:00
@noreplay 当前 Java 语法易用性已提升很大了,还有几个特性我比较关注,你可以了解下(虚拟线程、外部函数&内存 API) - https://congci.com/main/home/topics/java-programming/
|
4
Biluesgakki 2023-06-05 08:43:26 +08:00
@wangxiaodong 升级 jdk 是个大问题。。
|
5
enpitsulin 2023-06-05 10:17:24 +08:00
|
6
enpitsulin 2023-06-05 10:18:57 +08:00
@enpitsulin 这些 before 和 after 的对比太搞了,实际上并不是这样的意思 简单一看真以为特性下放了😂
|
7
Senorsen 2023-06-05 11:19:00 +08:00 感觉这些年 Java 在努力追赶 C#、Scala 、Kotlin 、Go 等语言的特性啊,这么一看还是 Kotlin 之类的方便,升级新语法和特性,只需要更新个项目依赖就能享受到,底层还是基于 Java 字节码,而 Java 的升级还是太笨重了
|
8
boatrain1111 2023-06-05 11:54:56 +08:00
哪里复制的公众号文章
|
9
leonshaw 2023-06-05 12:12:36 +08:00
为什么不返回一个 int 呢?
|
10
K1W1 2023-06-05 12:16:09 +08:00 via iPhone
他强任他强,我用 java 8
|
11
zjp 2023-06-05 12:18:56 +08:00 via Android
#8 虽然 v 站上引流外站的很多,但是是不是原创自己搜一下内容就知道了。上来就说复制的真膈应人
|
12
vsitebon 2023-06-05 12:21:58 +08:00
@boatrain1111 这个就是作者
|
13
HikariLan OP @boatrain1111 确实是自己写的啊哥们...
|
14
john6lq 2023-06-05 14:14:12 +08:00
简单说,就是支持 main 重载并以它作为启动入口了呗?
|
15
Masoud2023 2023-06-05 14:53:37 +08:00
扫了一眼这个 JEP ,还是觉得不要去为了解决问题而创造问题,如果仅仅是只有一个 main 函数可以放进这种 unnamed class ,那怎么继续解释为什么 Java 的其他方法不能也想这样直接脱离 class 写出来?本就可以两三句话说明白的事情,非得上升到这种层面上去,我觉得这个 JEP 仅仅是想找个话题随便水一下 JEP 而已。
喜欢这种函数式编程可以去写 python ,作为一个面向对象的语言,就不要去东施效颦去学面向过程的东西了。 |
17
HikariLan OP @Masoud2023 可能是我没讲清楚?其他方法也是可以放进 unnamed class 里的,只不过没意义,因为只有 main 方法才能作为特例被启动(当然此时 main 方法调用 unnamed class 内的其他方法也是可行的)。
虽然但是,其实我个人也是觉得这个 JEP 蛮鸡肋的,用途不大。这篇文章只是因为看到社群中很多人认为这是 Java 21 的一个新语法糖进而产生了误解,故撰文解答之。 |
18
wangxiaodong 2023-06-05 14:59:24 +08:00
@HikariLan
简化的几乎可以当脚本使用了: #!/usr/bin/java --source 17 void main() { System.out.println("Hello, World!"); } |
19
wangxiaodong 2023-06-05 15:06:24 +08:00
@Masoud2023 Java 的虚拟线程其实也做到了对 Thread 的最大化兼容,这次的 JEP 我能 Get 到 @HikariLan 的关注点,就是尽量以改动最小的角度来改造 Java 。
|
20
HikariLan OP @wangxiaodong 其实这种情况你可以使用 Java9 新增的 jshell 来写的(如果你注意原 JEP 最下面的 alternatives ,第一个就是 jshell )
|
21
wangxiaodong 2023-06-05 15:30:46 +08:00 @HikariLan jshell 的用法、参数也是学习成本,我喜欢更广泛的 Linux Shebang 写法 #!/usr/bin/java --source 21
另外,我喜欢引入的与“未命名包”对称的“Unnamed Classes”概念,满足奥卡姆剃刀定律:如无必要,勿增实体。 |
22
netabare 2023-06-05 15:38:20 +08:00 via Android 感觉不如 async/await 或者 auto prop 。
|
23
Masoud2023 2023-06-05 15:57:45 +08:00
@HikariLan #17 好奇这种 unnamed class 铺开之后,没有 class 的 java 将来怎么组织 project structure🤣
|
24
allenzhangSB 2023-06-05 16:10:28 +08:00
你们这些人就不会好好写标题么?
|
25
nothingistrue 2023-06-05 16:23:32 +08:00 main 方法还好说,只是扩展了 JVM 执行入口,影响范围很小。未命名类就扯淡了,这增加了一个语法条款,但这条款仅仅是为 main 方法服务的。
这个措施,比编译替换语法糖可是要糟糕得多,架空 JCP 以及又额外搞出个 JEP 的 Oracle 就是能这么任性。 |
26
HikariLan OP @Masoud2023 铺不开的,这玩意只是个方便新手学习的妥协罢了。
就像我们从来不用 unnamed package 一样 |
27
HikariLan OP @allenzhangSB 唉,我也挺无奈的,正经点又没人看,只能尽量正文正经了
|
28
StevenQAQ 2023-06-05 17:02:44 +08:00
是好事,初学者难度降低,开发者也可以适当的使用简单的语法糖来处理一些简单的问题,方便高效。JEP445 是好东西。
|
29
wangxiaodong 2023-06-05 17:16:00 +08:00
@nothingistrue 既然有“未命名包”,完全可以有“未命名类”啊,一点理解负担都不会增加,而且还让人感觉 JEP 作者有始有终,将来出现个“未命名方法”,也是自然而然的啦! so ,并不是为 main 方法专一引入的。
|
30
nothingistrue 2023-06-05 17:29:33 +08:00
|
31
wangxiaodong 2023-06-05 17:35:48 +08:00
@nothingistrue 未命名包很早就有了,我就经常用,这次推出个未命名类,我觉得一脉相承,影响并不大。我不关心 JEP 和 Oracle 的关系,而只关心 Java 的实质改动,不太认同你所谓的“糟糕...任性”,反而我觉得这个 JEP 来的太迟了。
|
32
KevinBlandy 2023-06-05 18:01:10 +08:00
害,整了一阵子 golang ,倒喜欢上了 go 这种简洁的感觉。
|
33
FrankAdler 2023-06-05 22:00:03 +08:00
实际开发跟 main 函数打交道的时间 0.000000000000000000000000000001%都不到,所以真的不在乎这一点冗余了
|
34
liuliuliuliu PRO C# 2021 年就实装的特性,而且连 main 都不需要
|
35
wxlwsy 2023-06-05 22:41:55 +08:00
我不理解,有些人,多写几个单词好像就是原罪. 多个括号就是异端.
|
36
acerphoenix 2023-06-06 09:51:36 +08:00
很不赞成,java 的基础就是类,这概念入门并不难,静态 main 方法就是入口,多大多复杂工程也是这么个入口,一招鲜的概念。非得再搞不伦不类的第二种,入门不学规范写法,学个这么 trick 的写法,徒增认知负担。
|
37
wangxiaodong 2023-06-06 12:44:48 +08:00
@acerphoenix 使用者没任何负担和语法增加,就是少写个 class{}而已,JVM 解析辛苦点而已,但 JVM 不就是干脏活累活的嘛。别的不写类直接执行 println 函数的编程语言,是不是全部都要被你打上"徒增认知负担"!?
|
38
acerphoenix 2023-06-06 13:43:58 +08:00 @wangxiaodong #37 基本简单的标准,做一件事只有一种方式。要做减法,这倒是减了一两行代码,却要增加一种认知,只能说这个减法做的非常不聪明。以后还得学现有方式,你怎么说出没有任何负担的?照你说了这个也就少写个 class ,那现在的方式也就多写个 class ,能有多复杂?
别的语言不写类是因为本身就不用写,这里是多余的加了一种没必要的认知,这俩完全不一样的概念你是怎么想到放一起类比的? |
39
wangxiaodong 2023-06-06 17:24:15 +08:00
@acerphoenix 没有说服我,但感谢你码字的时间,至少这个 JEP 是不可阻挡了。哈哈,🤝。
|
40
bv 2023-06-07 19:52:53 +08:00 @acerphoenix #38 你信不信,有了简化 main 的写法,极有可能的结果就是非但不能减少初学者的心智负担,相反还可能加重初学者的心智负担。以后很有可能会有以下这些新手八股文:main 函数的发展历史;简化 main 与 class main 有何异同;为什么要简化 main ;简化 main 的底层原理;当一个包内有多个 main 时加载顺序。
|
41
zxCoder 2023-06-09 15:23:41 +08:00
没啥意义
|