微信搜索【程序员囧辉】,关注这个坚持分享技术干货的程序员。
最近看见一道有意思的面试题,是关于自增操作的,让我回想起以前自己也遇到过,并且曾经也让我困惑过,今天拿出来跟大家分享,希望对大家有帮助。
题目
我相信有不少人会认为输出是100,但实际运行输出是0。为什么了?要知道其中的原理,我们需要先了解下栈中的局部变量表(本地变量表)和操作数栈。
局部变量表和操作数栈
在介绍JoonWhee:Java虚拟机:Java内存区域的Java虚拟机栈时,我们说过“每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息”。局部变量表很容易知道就是用来存储方法中的局部变量,而操作数栈则是用来进行各种运算。
解惑
该题目中循环自增部分对应的字节码如下。
图中三个红圈的字节码从上到下分别对应:“j = 0”、“i = 0”、“j = j++”。可以看到“j = j++”共执行了3个步骤:
- 将第1个本地变量推送至栈顶,即将 j 推送至栈顶,此时 j = 0。
- 将第1个本地变量递增1,此时本地变量表里的 j = 0,递增完后 j = 1。
- 将栈顶数值存入第1个本地变量,此时栈顶的 j = 0,而本地变量的 j = 1,将栈顶的数值存入本地变量,则本地变量的 j 值被覆盖,变为 j = 0。
看完以上3个步骤,相信你已经知道了,为什么这个题目的输出是0了。
上图红圈对应的7行字节码执行过程,如下图所示,从左往右,每一列对应1行字节码。
扩展
如果我们把上文的“j = j++”改成“j = ++j”、“j++”、“++j”会有什么样的变化了。
案例1:改成“j = ++j”后,字节码如下:
可以看到“j = ++j”也是执行了3个步骤:
- 将第1个本地变量递增1,此时本地变量表里的 j = 0,递增完后 j = 1。
- 将第1个本地变量推送至栈顶,即将 j 推送至栈顶,此时 j = 1。
- 将栈顶数值存入第1个本地变量,此时栈顶的 j = 1,本地变量的 j = 1,将栈顶的数值存入本地变量,则本地变量的 j 值被覆盖,还是 j = 1。
因此,如果将题目中的自增操作换成“j = ++j”,输出结果为100。
案例2:改成“++j”后,字节码如下:
可以看到“++j”只执行了一个步骤,就是将第1个本地变量递增1,此时本地变量表里的 j = 0,递增完后 j = 1。
案例3:改成“j++”后,字节码如下:
可以看到“j++”只执行了一个步骤,就是将第1个本地变量递增1,此时本地变量表里的 j = 0,递增完后 j = 1。
看完几个案例后,我们知道了,造成“j = j++”的结果为0的原因是:先将 j 的值放到了操作数栈,然后对本地变量表里的 j 进行递增1,最后将操作数栈的旧值覆盖掉本地变量表里的新值,导致 j = 0。
相关字节码
最后介绍下文章用到的几个字节码。
其中字节码开头的 i 代表int,中间的store、inc、load代表一个操作,结尾的数字则代表具体数值或位置的下标,例如0代表第1个位置。