59

Java第一课

 6 years ago
source link: http://bridgeforyou.cn/2018/06/10/Java-HelloWorld/?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

你的解释不是我想要的

“同学们好,我是教授你们Java101课程的S老师。下面开始我们的第一堂课吧。”

“Java安装、编辑器安装、以及运行起hello world代码,我已经在课前预习邮件了告诉大家怎么做了,不知道大家完成的怎么样?”

“老师,您的邮件里就一句话,‘请自行Google’ …”

“没错。”

其实我内心OS是:如果台下大部分学生,都完成不了预习任务,嗯,那这门课又开不成了,我又可以安心做研究。

不过为了让这个故事继续下去,我们姑且假设大部分学生都完成了预习任务吧。

“嗯,同学们很出色,下面再来一起看看这两段Hello World代码”

HelloWorld-1:

public class HelloWorld {
    public static void main(String[] args) {
        int i = 0;
        i = i++;
        System.out.println(i);
    }
}

HelloWorld-2:

public class HelloWorld {
    public static void main(String[] args) {
        int i = 0;
        i = ++i;
        System.out.println(i);
    }
}

“相信大家也都知道运行结果了, 第一段代码是0,第二段代码是1 。好,我们的第一堂课就是这样,大家还有什么疑问吗?”

大概过了半分钟,台下有个同学问道,“老师,我想知道为什么?为什么只是换了下顺序,结果就不一样了?”

这是我期待已久的问题,对,就是简简单单三个字,“为什么”

旁边一同学,说道,“这个我知道。i =i++,会先赋值,再加一,所以结果是0,而i = ++i,会先把i加一,然后再赋值,所以结果是1”

全场感叹,都向那位同学投以敬佩的目光,毕竟他的理论足以解释现象。

唯有刚刚提问的同学,说了一句,“ 你的解释不是我想要的 ……”

翻译官

这堂Java第一课的高潮终于到来了,我很激动。

刚刚这位同学的解释,不可谓不对,但是终究没说到点上。

i =i++,会先赋值,再加一,所以结果是0, 这个解释很正确,但是理由在哪?

这只是你的片面之词呢?还是道听途说所得?这个解释不足以服众。

你写的代码,是高级语言,是给人看的,机器可看不懂。

所以在你写的代码,到机器开始执行中间,肯定有一个翻译的过程。

Java中,这个翻译的动作,是由JVM,Java虚拟机来完成。

大家都知道Java是跨平台的,所谓“Write Once, Run Anywhere”, 同样一份代码,可以在不同的平台上运行,不像别的语言,比如C,也许这段代码在Linux上正常,去到OS X就有Bug了。

那么Java是如何实现跨平台的呢?简单说,靠的就是JVM这个翻译官。

你写好的代码,会被编译成一个.class文件,也就是Java字节码文件,这里面记录的是一系列要在JVM执行的指令。

接着,你拿着这份字节码指令,去到任意一个JVM,Linux的JVM也好,OS X的也好,它们都会帮你把它翻译成对于平台的机器指令。这就实现了跨平台、

Java字节码是国际通用语言(英语),JVM是翻译官。

反汇编

回到我们的问题,++i和i++为什么会不一样呢?

这就要看这两行高级语言代码,转成字节码指令之后是什么样子了。

先来看看HelloWorld-1。首先使用javac把你写的高级语言,也就是java文件,编译成字节码文件。我已经把源代码中的System.out.println(i)删掉,这样我们就可以专心观察i++和++i:

javac HelloWorld.java

可以看到HelloWorld.java同级目录下,出现了一个HelloWorld.class文件。

class文件里面都是二进制的数据。为什么是二进制?因为这些都是告诉JVM要做什么事情的指令,而机器只看得懂0101之类的二进制。

所以,我们需要对这个二进制数据,进行反汇编,把它变成人类看得懂的语言,来看看这些二进制数据都在说些什么,这里我们用到javap:

javap -c HelloWorld.class

命令执行后,控制台打印出一系列的字节码指令,其中main函数的字节码指令如下:

public static void main(java.lang.String[]);
    Code:
       0: iconst_0
       1: istore_1
       2: iload_1
       3: iinc          1, 1
       6: istore_1
       7: return

这一串的指令,主要涉及到两个数据结构, 一个是操作数栈(operand stack),另一个是局部变量表(local variable)。前者是栈,后者是数组。

那么这些指令都是什么意思?

不急,下面图文并茂,给你解释。

栈和数组的故事

1、iconst_0

把一个值为0的int值,压到操作数栈中。

rUFFjab.png!web

2、istore_1

从操作数栈中弹出一个值,存放到局部变量表index为1的位置(为什么不是0,思考题)

pop之前:

qq6zY3M.png!web

pop之后:

fAFjeaI.png!web

以上两条指令对应的是第一行代码 int i = 0:

它实现了给i赋值,并且把i放到局部变量表的功能。

下面再来看看 i = i++ 对应的指令。

3、iload_1

把局部变量表中,index=1位置的值,压到操作数栈中。

fm2uYnn.png!web

4、iinc 1, 1

对局部变量表index=1位置的值,进行加1操作。

zyyyIba.png!web

iinc指令包含两个参数:

  • 第一个是index,代表要操作是局部变量表哪个位置的值;
  • 第二个是const,代表要加多少;

现在局部变量表里的i其实是等于1的,可是为什么最后打印出来还是0呢?

问题出在最后一条指令。

5、istore_1

从操作数栈中弹出一个值,将它赋值给局部变量表中,index为1位置上的值。

pop之前: rMbaeiJ.png!web

pop之后: QRB3iuF.png!web

完蛋,这下i又变成0了。

至于 i = ++i为什么最后是1 ,请大家按照上面的思路,自行分析。

其实两者的差别只在iload_1和iinc 1, 1的顺序上。

i = ++i,iinc 1, 1在前,iload_1在后,所以最后结果是1.

上面这些指令的含义,不需要刻意去记,有JVM规范可以查看: The Java Virtual Machine Instruction Set

这堂课提到的操作数栈和局部变量表,只是JVM运行时数据区域中,很小的一块,完整的模型图是这样:

fmeA3yF.jpg!web

操作数栈和局部变量表,位于图中的JVM Stack中,也就是我们常说的虚拟机栈。

End

这堂课的重点,并不在于跟大家解释i++和++i的区别,而是要给大家引入一个Java中十分重要的观察角度——JVM.

你写的代码,只是表象,程序不一定按照表象去执行。

万一发现很奇怪的现象了,莫慌,别忘了中间还有个JVM在作祟。

……

忽然,闹钟响了。

“傻蛋,怎么老是做这个梦。你早就因为开不了课被大学辞退了。”

起床,刷牙洗脸,上班。

今天又会有什么好玩的需求?

参考


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK