今天我终于了解了这两个式子在字节码指令中的具体操作原理,以前记忆这两个式子都是固定思维:i++是先进行运算再进行自增,++i是先进行自增再进行运算。但是今天发现了一个问题。
public class AutoAddTest {
public static void main(String[] args) {
int i = 0;
i = i++;
System.out.println(i);
int j = 0;
j = ++j;
System.out.println(j);
}
}
发现第一个i输出结果为0,j的结果为1。在我看来不管是i++或者++i运算之后都是会自增1的,但事实却不是这样。。。结果:
首先我们要知道Java虚拟机栈的内部结构,虚拟机栈是主管程序运行的,方法的执行与返回就对应着入栈与出栈,而Java虚拟机栈的基本存储单位是栈桢,每个栈桢都对应着一个方法,是一一对应的关系。
栈桢里面又细分为:
①局部变量表(保存方法参数和局部变量的一个数组)
②操作数栈
③动态链接
④方法返回地址
⑤一些附加信息。当然这里不是我们要讲的重点!重要的是这里我们只用到其中前两个相对比较重要的局部变量表和操作数栈。需要大家对这两个结构一定了解。
分析原因!!接下来分析字节码文件中的指令(下图由Jdk自带解析工具javap解析的class文件)
如图我们可以看到
**int i = 0**;编译之后为:**iconst_0,istore_1**。
意思是将常量加载到了操作数栈,然后将操作数栈顶元素保存到局部变量表索引为1的变量槽中
**i = i++**;编译之后为:**ioad_1,iinc 1,1,istore_1**
这些指令指的分别是:
ioad_1:将局部变量表中的i加载到操作数栈。
iinc1,1:局部变量表中的i自增1,最后istore_1将操作数栈顶的元素再赋值给、保存到局部变量表索引为1的变量槽中。这个过程操作数栈中的值并没有改变!所以最后赋值回去的时候,局部变量表的自增1相当于被覆盖了!
这个过程我们可以看出,i = i++无非就三个操作,加载入栈,表变量自增1,栈中数据重新赋值给i,相当于把在表中自增的操作给覆盖了。所以最后我们看到的结果就是i没有增加,如果这个时候我们不把结果重新存回原变量,而是存回其他变量比如j,这个时候,i就还是会保持自增1的操作。
++i也是经过三个步骤,首先局部变量表自增1,入操作数栈,出操作数栈保存到局部变量表。与i++不同的是,先自增1在进入操作数栈,那么这个时候再赋值给原变量i结果也是加1了的,如果这个时候不进行赋值保存,而是进行其他运算操作,那就是我们理解记忆的先对变量自增1,再进行运算了。