第2章 第一行代码

编程难,纹身大哥胆寒!

枪林弹雨何所惧,更喜狱友菊花残。

垂死病中惊坐起,程序崩的没道理。

为什么会有这首打油诗呢?因为我看到一个新闻,讲的是地球的另一边,美国,有https://thelastmile.org这样一个 项目,如果你犯了事,被关进了监狱。监狱里就会培训你:编程!

听说,这大大降低了犯罪率,因为很多惯犯出狱之后,一想到再次入狱,要去学编程,就放弃了作案的想法,那可太痛苦了,宁可死刑也不愿意去监狱学编程!

本章的主要目的是把开发环境给跑起来,用Hello World肯定学不到什么东西,但是至少能检测开发环境有没有跑起来。

Hello World的由来

1974年,美国贝尔实验室,一位名叫Brian Kernighan的研究员,他写了一份名为《Programming in C – A Tutorial》的文档,在这个文档的第二段,他用C语言写了一个经典的程序,程序是输出Hello World这两个单词。

后来这个研究员又写了一本风靡全球的书叫《The C Programming Language》,在这本书里,他再次用了上面提到的程序,从此,几乎每一个编程语言的第一个程序都是输出hello world这两个单词。

写这个程序的目的是测试一下开发环境配置的是否正确。如果能输出这两个单词,至少说明“大概率”可用。本章的目的也是让开发环境跑起来。

1 “传统”Java版的Hello World

其源代码如下:

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}

这一章,我们要做的事情是搞明白: 用什么工具,把上面这段代码输入,输入之后,用什么工具把这些代码整成能运行的“软件”.

1.1 用什么工具来输入代码?

简单来说,用VSCode或者IntelliJ IDEA,前者是一个编辑器(虽说是编辑器,但是比十年前的IDE,比如Eclipse,NetBeans的功能强太多了),后者是一个IDE。说实在的,不管这两个你用哪个,都已经是非常强大了,强大到无论你用哪个,都可以通过菜单把代码运行起来。

Eclipse VS NetBeans是当年争锋的两大IDE

Eclipse最初是由IBM开发的内部工具,在2001年发布为开源项目。Eclipse的设计师是 Erich Gamma,同时他也是JUnit的作者。Eclipse的精髓是插件,但是Eclipse的插件可以进入主进程,一些“水平很一般”的程序员,可能就把主进程给写坏了。

后来VS Code就吸取了经验,等等,为什么VS Code能吸取经验呢?因为VS Code是Erich Gamma跳槽到微软做出来的。

至于NetBeans,也是一个IDE。哎,本书的作者——就是我——曾经在Sun公司里去高校以及各种技术大会,比如Intel开发者大会,推广过Solaris和NetBeans,推广真的好难,背着宣传单,背着光盘,还得送人家巧克力。人家还不一定用,只是把光盘上送的巧克力吃了,出门就把光盘扔了。

我就不多介绍什么是VSCode和IntelliJ IDEA了,问问ChatGPT吧。如果开发Java的话,项目一旦变大,IntelliJ IDEA是更好的选择,这家捷克开发的软件,有收费版,也有免费的社区版,用免费的就足够了。

只需要点一下,代码就跑起来了,就跟自动档汽车一样,“傻瓜”都会开。

无论你选择哪个,我都建议你安装一些AI插件,这些AI插件可以自动帮你补全所有的代码。插件实在太多了,也没人给我广告费,所以我就不推荐了。我用的是GitHub的Copilot,但是这个每个月要收10美元。如果没人给你这个钱,就用免费的吧,功能大差不差,尤其是对初学者,都是用最简单的功能,没差别。

推荐用哪个工具?

没有什么好推荐的,我做事的原则是:大事看原则,小事讲风格

用哪个工具,缩进用空格还是用tab,显然都是小事,你有自己的风格就好。又不是什么家国大事,婚姻大事,如果在这些大事上有冲突,赶紧跑路,最好打车跑路。

1.2 输入的时候需要注意什么?

编程语言是“语言”的一种,而且是“最死板”的那一类。错一点,也不给你运行。所以,对初学者,有以下需要注意的地方:

1.2.1 源代码的文件名要与类名严格一致

如果你是初学者,可能会好奇,什么叫类名?没关系,类名就是上面代码中 class 后面跟着的HelloWorld。java的源文件后缀名为.java。所以,源代码的文件名为HelloWorld.java。

1.2.2 要注意全角与半角符号

在源代码中有分号、逗号、花括号……这些都应该用英文的半角字符,而不能用中文的全角字符。在英文中,这些符号都是半角——在排版时占据一个字符的宽度。而在中文中,这些符号都是全角——在排版时占据两个字符的宽度。

我当年初学的时候,在这上面可吃了大亏了。

1.2.3 可以用AI补全

要是搁在以前,我都是建议别人手打一遍代码,受受苦,但是现在,真是用不着了。AI目前这么强大,现在你不让他干活,到时候它强大到足够奴役人类的时候,可不一定会让你清闲哦。早享受总是对的。

1.3 如何让源文件跑起来?

用的工具是JDK,全称为Java Development Kit(JDK),这是Sun针对Java开发人员发布的免费软件开发工具包(SDK,Software development kit)。自从Java推出以来,JDK已经成为使用最广泛的Java SDK,即使后来Sun公司被Oracle收购了,也叫JDK,只是叫Oracle JDK了。

至于如何安装JDK,我就不废话了,请打开ChatGPT这类大语言模型,输入类似“ 如何在Windows上配置Java开发环境?”大语言模型处理这种事情,特别拿手。

如果你使用的是IntelliJ IDEA,可能你连装JDK的机会都没有,IntelliJ IDEA当检测到你的电脑没有JDK的时候,会使用自己的JetBrains Runtime,这是一个基于OpenJDK的修改版本,作为运行环境。

无论是借助大AI来帮助你完成的开发环境配置,还是IntelliJ IDEA自动安装的,最终,都会安装几个软件,最重要的有两个,一个叫javac,另一个叫java。

javac的是java语言的编译器,其作用是将java源代码(就是前面那个HelloWorld.java)编译成字节码,如果你用命令行的话,命令如下:

javac HelloWorld.java

这条命令执行之后,会生成字节码文件(你会发现执行完以上的命令之后,在存放HelloWorld.java的目录下,多了一个叫HelloWorld.class的文件)。然后,再使用java来运行刚刚生成的字节码,命令如下:

java HelloWorld

这里需要注意的是,不需要HelloWorld.class这里的.class后缀。

但是现在没人还这样手工来处理了,都是使用VS Code或者IntelliJ IDEA,这些工具已经足够智能,完全自动的处理javac与java的执行过程。点个按钮,直接出结果。这当然简化了工作,但是也会让人搞不清楚到底发生了什么事。

各有利弊吧。希望你在VS Code上,或者IntelliJ IDEA上看到了“Hello World”这两个单词。如果没有,恭喜你,你碰到bug了。看下一小节。

1.4 碰到bug了怎么办?

如果你认为确实没有任何问题,但是就是不运行,哥们,第一件事情:重启(先重启开发环境,再重启电脑)。可能会有其它同事说你,你TMD就是会重启,这时候,如果你觉得有必要跟他争论,就以下面的理论为基础进行人身攻击。

图灵奖得主Jim Gray写的论文,名字叫《Why Do Computers Stop and What Can Be Done About It》,我把这篇论文放在我的社区xueban.app里。在这篇论文里,Jim Gray认为,bug为两种,一种是玻尔bug,另一种是海森堡bug。

Jim Gray简介

Jim Gray是第三位因推动数据库技术而获得图灵奖的人,在计算机方面有非常多贡献。他爱好冒险,在2007年1月28日,他驾驶着自己40英尺的船,消失在茫茫大海,至今杳无音讯。5年后的2012年1月28日,Jim Gray在法律上被宣告去世。

我平时还有个爱好是录podcast,名字叫“软件那些事儿”,我录了快500期电台了。有一个系列叫图灵奖得主介绍,就介绍每一年图灵奖得主的信息,如果你有兴趣,可以搜来听听,或者到liuyandong.com上看文字版。

按照Jim Gray的分类,玻尔bug是能通过测试,容易复现,也比较容易解决的bug。海森堡bug是不确定的,不容易复现,不容易解决的bug。正如两位科学家所研究的理论一样,一个如原子结构般确定,一个如电子行为般不确定。

如果重启能解决,那我们碰到的就是玻尔bug。

如果重启还没有解决问题,一般来说,把报告的错误信息放到搜索引擎里,是个好办法。初学者,碰不到什么新鲜bug,99.9999%的情况下,咱们碰到的bug都是别人碰到,并且解决过的。

但是也不否认你确实碰到了别人没有碰到的bug,虽然机率确实很低,而且是用HelloWorld这种代码碰到的,概率就更低了。

不过,凡事不能太绝对,比如,在Java的历史上,就有一个至今都没修复的bug,这个bug是初学者就能碰到的。有兴趣的读者可以到Java的bug系统上去看看,网址是:https://bugs.java.com/bugdatabase/index.jsp 输入这个bug的编号:4252539.

按照Java语言的规则,main函数必须被声明为public,但是软件难免会出现bug。在Java 1.4之前的一些版本里,public不被声明为public也可以运行。这个bug从来没有被修复过,主要原因是如果修复了,可能会导致其它不可预知的结果。不少bug就是会被置之不理的。

苹果手机里也有“没人理”的bug

如果大家用iOS 1582 calendar作为关键字来搜索,会找到这个bug的详细介绍。

简单来说,在1582年,人类的日历从儒略历换成了格里历。这就导致在1582年的2月份出现了2月31日。iOS系统没有恰当的处理这个bug,以后不知道会不会处理,但是谁会在意1582年的日历呢?

同样,bug足够出名,还会成为feature。(你足够出名,写的错别字就会成为通假字,人是一种贱嗖嗖的生物,欺软怕硬。鲁迅写就是通假字,你写就是错别字被都是扣1分)。

在软件史上,有很多类似的情况,bug成了feature,将错就错。比如Unix上的creat,本来应该是create,但是少写了一个e,后来所有的Unix变种都将错就错的少写了一个e。后来有人问到Unix的作者Ken Thompson如果你要重要设计Unix,你会做哪些改变,他回答说把creat写成create。这个故事来源:https://en.wikiquote.org/wiki/Ken_Thompson

2 八仙过海的Java虚拟机

这段是八卦,如果你没时间,不用看。如果用来八卦的话,还是稍微有点用处的。或者只需要看看最后我的感慨就可以了。

比如,现在大语言模型是热点,那么,所有有能力的公司都要搞自己的大模型出来。行不行是一回事,有没有是另一回事。如果不行,在牌桌上顶多是个送财童子;如果没有,那可就连牌桌都上不去了。

同样的道理,Java在当年也是热点,而且还开源。只要你敢开源,洒家可就敢自主创新了。Java的标准大家都要“尽量遵守”,也有故意掺沙子,甩石头,挖墙角的,比如微软当年搞的J++语言,就是想把Java搞黄。但是Java虚拟机的强弱,就要各显神通了。

微软的J++语言与C#语言

以前有个叫Borland的公司,其灵魂人物叫Anders Hejlsberg,跟很多大佬一样,是个辍学生。Borland公司跟微软有场官司,控告微软挖走了公司30多名重要雇员。这些雇员中,就有Anders。最后Borland赢了官司,但是输了战争,从此销声匿迹。

Anders去了微软以后,操刀负责J++项目,这个项目从名字上看就是对标Java。而且是Java的变种,其语法、关键字与Java完全相同,讲究一个微创新。Anders的功力是不容小觑的,在他的操盘下,外加微软的无限资金与无限人才,J++很快就做到了当时Java所能做到的一切,并且在很多方面都超越了Sun的Java。完爆这个词不好随便使用,但是说一句青出于蓝而胜于蓝,是不夸张的。

从技术上来说,我觉得微软胜;但是从道义上来讲,微软输。因为当时Sun给微软的协议,要求微软不能魔改Java,要遵守标准。而且,微软那时没打算把这些技术用在Windows之外的平台上,那时跟Linux是水火不容,更不要说给Linux提供支持了。于是,法庭见,微软输了。

于是J++项目停止,C#项目出生。操刀人依旧是Anders。于是纷争,就成了Java与C#的竞争,这么多年过去了,若论技术,我仍然觉得C#要比Java高明一点,但是微软多年来对开源社区的态度,以及经常跟合作伙伴上法庭,所以,C#就是没法压Java一头。

后来,又有一家公司试图把Java给分裂掉,这家公司就是Google,只是Java的东家换成了更善于打官司的Oracle,那就是另一个故事了。

我们写的代码最终还是要跑在Java虚拟机上。现在业界有很多种Java虚拟机,在维基百科的页面上,活跃的JVM还有9种,不活跃的JVM有16种,我相信还有更多没有统计在这个页面上的JVM。JVM和Java语言是双子星,他们相伴相生,一损俱损,一荣俱荣。如果你对故事感兴趣,可以读一下Java虚拟机的故事。

2.1 Sun的虚拟机

1996年,Sun发布Java语言的同时,还发布了世界上第一个Java虚拟机。这个虚拟机之所以重要,是因为是世界上第一个JVM。缺点也很明显,这个JVM的执行效率非常差。

随后,Sun公司开始对Java虚拟机进行改进。改进的成果是一款只可以运行在Solaris上的JVM,名叫Exact VM。如果大家用Exact VM在Oracle的官网上搜索,仍然可以找到一些信息。大部分信息都是介绍Exact VM和HotSpot在编译时如何设置参数。

但是Sun没有再继续推广这款名为Exact VM的虚拟机,原因是一个更为先进的虚拟机出现了,这个虚拟机也是目前最为流行的Java虚拟机之一,名字就叫HotSpot。

HotSpot并不是Sun公司做的,而是一家叫Longview Technologies的产品,这家公司的创始人,一个叫Urs Hölzle,一个叫Lars Bak,两人从Sun离职后创业。为了重新获得这两个员工和他们的产品,Sun收购了这家公司。

该产品原本是针对SmallTalk语言做的虚拟机,被收购以后重新设计,转而支持Java语言,跟随Java 1.2的同时发布。

Java的1.2版本,有3个JVM,一个是Sun公司第一版的虚拟机,一个是Solaris上的Exact VM,还有一个就是HotSpot。默认的JVM是最慢的那一个,直到1.3版以后,才将默认的虚拟机换成HotSpot。

在早期,只有Sun公司有JVM,其它公司没有“版权”来染指Java。直到1998年,Sun公司成立了JCP(Java Community Process)组织,这个组织希望让越来越多的公司参与进来,大家都分一杯羹。当然了,Sun公司肯定是想做分羹的人,大家做Java可以,但是要通过TCK(Technology Compatibility Kit),翻译成中文叫技术兼容性测试。

在TCK的约束下,不少公司通过了TCK,也号称自己的虚拟机叫JVM。在这个背景下,有很多公司推出了自己的Java虚拟机。最著名的厂商有IBM、BEA、Microsoft和Apache。

IBM的J9虚拟机

IBM官网上介绍,这款JVM最早是由IBM Ottawa实验室一个SmallTalk的虚拟机扩展来的。

那时候,这个虚拟机有一个bug是因为8k值定义错误引起,工程师们花了很长时间终于发现并解决了这个错误,此后这个版本的虚拟机就被称为K8了。

后来出现的支持Java这个版本的虚拟机就被称为J9了。

与Sun公司收购HotSpot类似,IBM的J9也是收购的。1996年,IBM收购了一家叫OTI(Object Technology International)的公司,该公司有虚拟机产品。更巧合的是,这个产品最初也是为SmallTalk语言设计的虚拟机,后来在Java流行以后,才改为支持Java虚拟机。

2017年,J9变成了IBM主导的Eclipse组织的一个项目,名字已经改成了Eclipse OpenJ9。现在建立在Eclipse开放运行时项目(OMR)之上,IBM的专有项目大部分基于此,它完全兼容Java认证。

2.2 BEA的JRockit

BEA公司目前已经被Oracle收购,所以,Oracle拥有三个最主要的虚拟机中的两个。

BEA公司是著名的Java中间件公司,曾经是IBM公司最重要的竞争对手,产品是与IBM WebSphere竞争的WebLogic,IBM有自己的Java虚拟机,BEA也想有自己的Java虚拟机。

收购是最省时间的方式,BEA就收购了Appeal Virtual Machines公司,这个公司的产品是JRockit。

从名字可以猜一下,应该和火箭一样快吧。JRockit的特点就是速度快,针对的市场是用专门硬件,专门服务器的商业用户,不针对消费者市场。这个公司宣传自己的产品是:“World’s Fastest JVM”,世界上最快的Java虚拟机。

BEA被Oracle收购以后,就被Oracle暂停了,Oracle没必要同时拥有两个Java虚拟机。在2011年,Oracle宣布JRockit可以免费使用,但是由于多年没开发,JRockit最高只能支持Java 6。Oracle承诺,会将JRockit的优秀特征在OpenJDK实现。

2.3 微软的JVM

微软也出过Java虚拟机,并且性能还相当不错,在1997和1998年获得过《PC Magazine》杂志的编辑选择奖,在1999年宣称自己是Windows上最快的Java虚拟机。

微软为什么会花大力气帮助Sun来实现Java虚拟机呢?答案当然是想控制Java了。因为Java,Sun在1997年控告微软违反协议滥用Java。直到2001年,微软败诉,赔偿2000万美元给Sun公司。

后来微软山寨了Sun的Java,强推自己的Visual J++,官司输了以后,又开发了J#和C#,再推广JVM对微软已经没任何正面意义,所以,微软的JVM在2003年就停止开发,最晚支持到2007年。

有个大翻转比较有趣,Sun在赢了官司以后,按照协议,Windows XP不能预装JVM。Sun此时才回过味来,如果不预装JVM,那么对于推广Java百害而无一利,于是又开始劝微软继续装JVM。

那时Sun公司已经发布了Java 1.4,微软只肯在Windows XP Service Pack 1中包含一个1997年的,基于Java 1.1.4版本的JVM。

2.4 Apache的Harmony

Apache也有JVM,但是却不能称之为JVM,前面讲过,想宣传自己为JVM,要先得到JCP主导的TCK兼容性测试。Apache得不到这个认证。

为什么会得不到这个认证呢?主要还是理念问题。

JCP的执行委员Doug Lea如此评价Oracle:“虽然Sun Microsystems已经制定了可以推动JCP创新的规则,但是Oracle并不理会这些规则,JCP也许会成为任Oracle摆布的傀儡。”

Apache组织是个非盈利组织,Oracle是个以盈利为主要目的公司。理念谈不拢。Apache希望Java能够不受任何公司的控制,让Java完全开源,做了名为Harmony的Java版本。

后来,Java的创始人James Gosling也建议Oracle应该成立一个独立的JCP来控制Java,但是Oracle不为所动。2010年,JCP开会讨论Java 7和Java 8的方向,这次会议双方的矛盾最终爆发,Apache宣布退出JCP,Oracle乐见其成。

本来IBM,Apache和Google是推动Harmony的三巨头,但是IBM却发表声明说今后将退出Harmony,以最大的努力推动Oracle的OpenJDK的发展。随后,IBM辞去了Harmony项目主席的职位。

Apache一方面无法得到TCK认证,另一方面,最坚定的支持者之一IBM也跑去了Oracle的阵营。在这种境地下,2011年12月16日,Apache宣布取消Harmony这个JVM的开发。

该项目包含一个名为动态运行时层虚拟机(Dynamic Runtime Layer Virtual Machine,DRLVM)的Java虚拟机实现,从官方文档介绍上说,该实现对Java 6类库的完成度超过了97%,我在写书的时候,花了不少时间来研究这个97%是如何计算的,没找到更详细的信息。

Apache有一个坚定的支持者,就是Google。Google的安卓系统是Java的最大用户之一,他们始终不愿意让Oracle掌握命脉,于是,他们坚定的走Apache的Harmony道路,开发自己的JVM。

2.5 Google的Dalvik

Android是Google最大的资产之一,凭借Android,Google掌握了手机市场。Sun公司虽然一直想把Java推广到手机中,但是应该没有想到Google把这事做成了。

Dalvic名字的起源

Dalvik由Dan Bornstein编写的,名字来源于他的祖先曾经居住过的小渔村达尔维克(Dalvik),位于冰岛埃亚峡湾。

在Apache宣布不再继续Harmony虚拟机以后,Google从中获取了大量的代码添加到Google Android SDK中。在2010年JCP投票中,Google和Apache坚定的站在一起反对Oracle,Google也就顺理成章的没有获得TCK认证。当然, Google的Dalvik虚拟机压根就没打算取得Oracle的认证,再加上后来的Android Runtime,Google从不承认自己的产品是JVM。

情况确实有点复杂。除了Harmony虚拟机,Android还使用了一个交叉编译器来生成支持非Java虚拟机的不同的文件格式dex。

从2015年开始,Google已经不再采用Apache Harmony的类库,转而采用OpenJDK。无论如何,我总觉得它是一种魔改版的JVM。

介绍了前面几种JVM,这只是其中一部分比较出名的。据我所知,有非常多的Java虚拟机,因为相对于庞大的Java类库,虚拟机的实现成本要小的多。相比于数量众多的Java虚拟机,历史上只有三个独立的Java类库,它们分别是:OpenJDK、GNU Classpath和Apache Harmony。目前仍然活跃的类库只有硕果仅存的一个:OpenJDK。

2.6 我的感慨

现在的JDK更多,如果你去搜的话,出名的至少数十个,不知名的就更不计其数了,每家稍微大点的公司都有自己家的JDK。

为什么会这样呢?因为每家公司都不能依赖别人,是的,不止每家公司,每个人都不能依赖别人。中国有句古话叫靠山山倒,靠人人跑。

在不久以前,开源的C++的编译器只有gcc这一家,苹果公司当时还没有现在这么有统治力,在苹果公司的开发工具xcode中,使用的是gcc这个编译器。那……会有问题么?

就像蛋蛋被别人捏住一样,虽然你的朋友可能不会捏,但是你总担心万一哪天,他一使劲,你不就完蛋了么?每当苹果公司需要gcc的某些功能的时候,gcc总是爱理不理,钱留下,功能,慢慢排期吧。于是,Apple也就不再纠结了,自己做自己的工具链。于是Apple开发了自己的LLVM,Clang,不用再仰人鼻息。

大的公司不但要有自己的JVM,还要有自己的编程语言,比如Apple家的swift,Google家的Go,Microsoft家有自己的C#……

当你认为别人可能会攻击你的时候,那他一定会攻击你。这是我看《动物世界》最大的感慨,动物从来不攻击年富力强的动物,专门挑老弱病残来攻击,要么就攻击带娃的动物。只要你有弱点,那么这个弱点一定会成为攻击对象。中国有名言:麻绳专挑细处断。

而人,是动物的一种。

你的年龄会成为劣势,你的子女会成为你的软肋,如果你上有老下有小,年龄还偏大,并且还有贷款,好吧,你就是最弱的那个人。老板会欺负你,这几乎是肯定的。因为你没有能力反抗,你的身后什么都没有,搁在国外,加入个工会什么的,企业还是比较怕的。

希望你能理解,为什么这些公司,拼命的要把握住自己的命运。也希望你能把握住自己的命运。

3 “脱口秀”之表扬与自我表扬

前面讲了Java语言,javac和JVM,了解程序员,javac和JVM之间的关系非常重要。下面我编了一个小故事,希望对理解这三者之间的关系有帮助。这个小故事的名字叫《表扬与自我表扬》,栋哥是程序员。

栋哥:有件事情我想和两位说一下,项目成功上线以后,咱们会有一个名额去东京旅游。我觉得咱们仨个这几年来一直在一起工作,我非常的感谢javac一直以来给我写的程序检查错误,给JVM输出最终执行的文件。当然了,javac很棒,JVM也非常棒,能一直稳定的运行咱们最终的项目。没有javac的检查,没有JVM的运行,我肯定没法完成任务,这些年来,我一直拿两位当兄弟看待!不过,我还是觉得我最有资格去东京,毕竟没有我,两位工作的再好,也是无本之木,无源之水。两位应该没什么意见吧?

javac:很好,一起工作有难同当的时候是兄弟,有福同享的时候就不是兄弟了,很好!不瞒两位说,如果不是我javac在两位之间做翻译,你们知道你们说的什么么?栋哥,不瞒你说,你写代码,如果有我输出的字节码质量的百分之一,我们还用天天加班么?你应该心里有数吧?从标点符号出错,到变量名出错,还有变量类型中的错误……这么说吧,如果不是我屏蔽了这些错误,你的代码如果直接给JVM去运行,他能天天死机。没有我,你们两个只能是最陌生的陌生人。

JVM:两位真是王婆卖瓜,自卖自夸啊,你们可真是厉害。我只想纠正两位一下,只要项目一上线,我就要24小时工作,你们工作再多,也不过是996么。两位既然自视甚高,我就不去旅游了,我还要工作呢。我只是提醒两位,没有人比我干活多。刚刚javac的发言,真是要笑死我了,你还当我们的翻译,对我来说,你就是个传话筒,栋哥你可以直接写字节码给我,咱们两个直接交流,还有javac说的那些错误处理,我都能处理。只要有错误,我就扔给你ClassCastExceptions,咱们之间,不用有些人传话,有道是,传钱就怕传少了,传话就怕传多了啊。

javac:哈哈,JVM你可真是可笑,你和栋哥直接交流,不是我说大话,栋哥用简单的编程语言写的代码都错误百出,你要是让他用字节码和你交流,他一天也写不出一行来。用字节码写程序,那就相当于搞活字印刷,先得让栋哥去学习如何用铅字排版,不,还没有铅字,得让栋哥学习如何将硫化铅提炼成铅。

栋哥:别吵了,没想到我在你们眼里是如此不堪。我并没有否认两位的功劳。我和两位不一样,你们俩只懂Java这一门语言,我是程序员,我还懂得C语言,Python语言,PHP语言……相比于两位来说,我可以说是站得高,看得远,用其它的语言中,其实也能实现两位的功能,比如说在C语言中……你们要干什么,打人是不对的,有话说话,不要动手啊,君子动口不……救命啊,打人了……

4 小朋友,你是不是有很多问号?

4.1 bytecode是什么?

我在IBM Developer上曾经看过一篇文章,文章的名字叫Java bytecode: Understanding bytecode makes you a better programmer在这篇文章里,作者说:“对Java程序员来说,理解bytecode类似于让C/C++程序员理解汇编语言”。

Kathleen Booth与汇编语言

Kathleen Booth女士在1947年发明了汇编语言,并且设计了伦敦大学第一个汇编程序与自动解码。

汇编语言是一种非常低级的语言,一般不能在不同的平台之间移植。可以把汇编语言看作是机器语言的助记符,在不同的设备上有不同的机器语言指令集。

“bytecode”里的byte有真实的含义,代表byte,也就是8个bit。8 bits会产生256个组合。Java虚拟机最多支持256个操作符。目前,只用了大概80%。可以把bytecode理解为“Java虚拟机的机器码”。

javac的工作就是把Java源代码转化成bytecode。 不管是在Linux上生成的bytecode,还是在Windows上生成的bytecode,都是一模一样的,这构成了java跨平台的基石。

4.2 javac是编译器么?

肯定不是gcc那样的编译器,javac不产生机器码。javac产生的是bytecode,如果大家了解Windows上的dll文件,或者Linux上的so文件,它们的作用非常相似。

在Java虚拟机中,负责产生机器码的是JIT(Just-In-Time)运行时编译器。

在本书中提到javac是编译器,我的意思是编译成字节码。也可以认为Java上实际上有两种编译器,一个是Java字节码编译器,一个是JIT编译器。

JIT编译器的工作原理是这样的:软件在运行的过程中,大部分时间用来运行少量的代码。当软件在解释模式下执行的时候,编译子系统会时刻监控软件的运行,并观察代码中执行最频繁的部分。在整个分析过程中,会捕获一些重要的信息,再根据这些信息进行优化,优化的原则是:把运行最频繁的部分,编译成机器码。这样一来,Java代码就拥有了可以和C/C++相媲美的性能。

多说一句,如果你多年未关注编程语言的飞速发展,可能错过了一些精彩的时刻。目前,主流的编程语言都已经支持JIT技术,像PHP 8,JavaScript,Python都已经开始引入JIT技术,这也是开源技术的魅力所在,只要一项技术被证实可靠,就会被其它语言采用。

对普通开发者来说,采用HotSpot的方法就很好。因为HotSpot采用的方法太复杂,普通开发者根本看不懂,看不懂就不会胡乱修改,反而不容易产生性能问题,只要跟着Java虚拟机升级就能获得益处。

4.3 Java虚拟机只能运行Java语言么?

以前是,现在不是。

Java虚拟机和Java语言现在已经渐行渐远,虽然Java编译的字节码只能跑在Java虚拟机上,但是Java虚拟机并不是只跑Java的字节码。目前来说,Java语言和Java虚拟机在一定程度上是独立的,Java虚拟机也许应该换个名字,比如叫“万物虚拟机”更贴切一些。

现在Java虚拟机可以执行任何语言生成的合法的文件,只要符合Java虚拟机的规范就好。比如目前比较热门的scala语言用其编译器scalac生成的字节码,完全可以运行在Java虚拟机上。

Java虚拟机如果要加载类,会先验证它们是不是符合规定的格式,如果符合,就允许其执行。本书不会对类文件的规范讲的太深入,只讲一个有点意思的事情吧。

在Windows上,可以用扩展名来识别文件类型,在Unix环境下,则要使用一些魔数(magic number)来识别。每个Java类文件都以魔数0xCAFEBABE开头的,这四个以十六进制表示的字符表示当前文件的类型。大家看到没有,最后四个字母是BABE,当年Java可能没想到会这么火,也没想到这个单词放在今天的舆论下,会涉及性别歧视。现在有转机了,在Java 9中,为模块文件(JIMAGE)引入了新的魔数0xCAFEDADA。也不知道“爸爸(DADA)”这个单词会不会在未来的日子里,也有歧视的意味。

比起社会的文化的变革速度,Java的更新速度其实很慢的,所以我们要长期使用到0xCAFEBABE这个魔数,不过,如果你不用十六进制编辑器打开二进制文件,是不会看到这个涉嫌性别歧视的字串的。顺便提一句,github这个网站,因为master这个词语涉嫌种族歧视,从2020年10月开始,新建的仓库的默认分支名由master改成main。

Warning

世界发展的非常螺旋🌀,一方面,BABE涉嫌对女性的歧视,Master涉嫌对当年黑人的歧视,纷纷做出了修改。另一方面,歧视好像没有减少,越不让说,通过罚款,把人嘴堵住的方式,会不会让歧视从显性到隐性呢?

比如,当年有一个叫Sarah Sharp的女性开发者对Linus抱怨,指责Linux内核开发者邮件列表让她感到不舒服。原因是,内核开发者都是一些“生活在黑乎乎地下室里的,只有地震、伽马射线与妈妈”才能伤害他们的人。Linus的邮件中,经常出现一些“不礼貌”的用词。后来失控了,Linus也被迫向女性朋友们道歉。

这件事过去十多年了,根据我的观察,即使Linus不太敢大嘴巴喷人了,女性内核开发者也没增加。(至少出名的内核开发者,还是那几个“老人”)。

5 “未来版”的Hello World

无可否认的是,相较于其他众多编程语言,Java语法确实展现出了更为严谨且相对复杂的特性。然而,在当今时代背景下,特别是伴随大型语言模型的应用普及,实际编程过程中,越来越多的代码编写工作不再完全依赖程序员手动输入,而是更多地扮演代码设计与审核者的角色。在此转变中,Java语言因其详尽且规范化的语法构造,恰恰有助于提升代码的可读性与一致性。从某种角度来看,这种所谓的“繁琐”特质实际上转化为了有利于团队协作和长期维护的优点。

尽管如此,Java依旧在简化自己的代码,在Java 21中,引入了新的语言特征,该语言特征可以使Java的Hello World用下面的方式来书写:

void main() {
    System.out.println("Hello world!");
}

由于这是Java 21新引入的特征,所以在编译与运行的时候,跟传统的方式有些不同,要告诉编译器与虚拟机启用新的特征。所以编译的时候要使用下面的代码:

javac --release 21 --enable-preview NewHelloWorld.java

–release 21 选项指示编译器使用 Java 21 版本的语言和 API。 –enable-preview 选项指示编译器启用 Java 21 中的预览功能。 运行的时候,也要加上相应的参数,用如下的命令:

java  --enable-preview NewHelloWorld

在Java中,java –enable-preview NewHelloWorld 命令用于编译和运行使用预览功能的Java源代码。 预览功能是指尚未最终确定并纳入Java标准的实验性功能。这些功能可能会在未来的Java版本中发生更改或删除。 –enable-preview 选项指示Java编译器和运行时环境启用预览功能。如果不使用此选项,则无法编译或运行使用预览功能的代码。

6 如果你需要使用多个版本的Java……

说实在的,只要你打工,什么屁事都能碰见。最大的可能是你公司里维护了3个版本的Java,这非常常见,为了跟客户的运行环境相同,你需要安装3个java,这时候推荐使用SDKMAN这个工具。

Tip

我打工的时候,碰到过用Java 1.2版的,到现在我都记得要手动转换int与Integer。考的是C证的本,结果人家整来一辆马车。

SDKMAN 是一个用于类Unix系统(包括但不限于Linux、Mac OS X、Cygwin、Solaris 和 FreeBSD)的强大工具,它旨在简化软件开发工具包(SDKs)的安装和管理过程。对本书来说,用SDKMAN可以很方便的安装JDK,其实,它还可以安装各种各样的流行框架与工具,在此不一一列举,安装以后,可以运行sdk list自行查看。

SDKMAN 的核心功能包括:一键安装、多版本管理 与快速切换。以Java为例,我来演示以下以上的核心功能。

一键安装是通过提供的命令行接口,只需一条命令即可安装指定的SDK版本,例如安装特定版本的Java JDK。比如,通过下面的命令可以安装JDK 21:

sdk install java 21.0.2-tem

如果你不确定有哪些版本的JDK可供安装,可以通过如下的命令来查看:

sdk list java

这条命令运行之后,会给出非常多的JDK供选择。我可以安装任意数量版本的JDK在我的系统中,用下面的命令可以查看目前的系统中安装了哪些JDK:

sdk list java | grep installed

如果要在不同的JDK中切换,可以使用如下的命令:

sdk default java <VERSION>

这里的VERSION只需要替换成你需要的并且已经下载到你电脑中的Java版本就可以了。

7 程序员故事

《潘新与潘闻两兄弟》

我的老板叫潘新,他有一个小他十二岁的弟弟叫潘闻。最近,潘闻来到他哥的公司实习,他也是学计算机的。老板让我带他弟弟熟悉一下公司和业务。

老板潘新的编程生涯是从汇编和C语言开始的,现在他已经不再写代码了,但是对编程有强烈的爱好。

他看了Java版的HelloWorld,问了我一个问题,Java的类名,函数名,变量名啊,能不能是中文的?

我觉得这是个好问题,正好可以给他弟弟找点事干,就扩展了这个问题:Java语言支持的Unicode字符可以用在什么地方?变量名可以么?函数名可以么?类名可以么?