前言
一个关于i++
和++i
的原理详解,对字节码不太熟悉的可以先了解下字节码相关知识,因为从字节码角度更能理解两者的原理。
案例
int i = 0, j = 0;
i = i++;
j =++ j;
System.out.println("args = [" + i + "]");
System.out.println("args = [" + j + "]");
输出结果:
args = [0]
args = [1]
对于i++
,++i
可能大家的第一反应的理解是:前者先运算再自增赋值,后者先自增赋值在运算。那么上面那个例子为什么会得出这个结果,下面查看其字节码,输出的我就忽略不展示了。
0: iconst_0
1: istore_1
2: iconst_0
3: istore_2
4: iload_1
5: iinc 1, 1
8: istore_1
9: iinc 2, 1
12: iload_2
13: istore_2
0 ~ 3行很容易理解,在局部变量保存i和j 分别设值为0。注意看4 ~ 8和9 ~ 13,他们分别代表的是i=i++
和j=++j
。他们之间的差别就在于load
指令一个在自增指令``iinc```前,一个在后
这里说一下iinc
指令,它是int类型的局部变量自增指令(将后者数据加到前者下标的int类型局部变量中)。可以看到这个指令和下一条指令之间左边的行号是少了两行的,那是因为这个指令显示的是一条,但是干了三件事,读取该值,自增加一,将自增后的值保存回局部变量。
下面开始分析,执行i=i++
操作时(4 ~ 8),首先iload_1
使局部变量i(0)
入栈,然后执行iinc注意,执行完iinc
后局部变量表中的i是已经变成1了,iinc
并不是吃干饭的,但是前一条指令入栈的值没变,还是0,然后紧接着对i做等号赋值操作的istore_1
指令将栈顶的0又保存回了局部变量1(i)
中,所以局部变量表中的值变化是:0 ~ 1 ~ 0;
不同的是,执行j=j++
操作时(9 ~ 13),先自增后局部变量表中j的值已经变成了1,然后执行=号赋值操作,将++j
这一整个iinc
后的结果也就是局部变量表2中的值入栈(iload_2
),然后赋值给j
(istore_2
)。
从上面的理解中可以看到俗称的:先运算在赋值和先赋值在运算
,其原理就在于iinc
指令是否先于局部变量的入栈赋值操作,下面再给几个拓展例子看看。
例一:
int i = 0, j = 0;
i=i++ + i++;
j= ++j + j++;
例二:
int i = 0, j = 0;
j=i++ + i++;
例三:
int i = 0, j = 0;
j=i++ + ++i;
例四:
int i = 0, j = 0;
j=i++ + ++j;
上面我不列答案,大家可以自己先猜测是多少,然后在看看自己分析的对不对。
例一:第一个自增和前面的步骤一样,入栈i然后自增iinc,但是第二个自增会重新把i从局部变量表入栈,此时i经过前面自增已经为1了,然后第二个自增后再赋值,结果就是i=0+1且此时局部变量表中的i值为2(这是等于号赋值前的值,赋值后又重新保存变为1了)。 j的运算我就简要说一下了,和前面类似:先iinc
自增运算(差异),然后j(1)
入栈,然后出栈保存,再入栈j(1)
入栈,自增运算,然后相加。结果是j=1+1
,且此时局部变量表中的j为2,下面是字节码:
4: iload_1
5: iinc 1, 1
8: iload_1
9: iinc 1, 1
12: iadd
13: istore_1
14: iinc 2, 1
17: iload_2
18: iload_2
19: iinc 2, 1
22: iadd
23: istore_2
例二:例二和例一不同的是最后的赋值对象从自身变成了其他人,所以这里和例一i运算解析是一模一样的,只不过是最后括号内那句话中的赋值对象变成了j,所以此时i=2,j=1。
例三:例三和例一j运算解析也是类似的,同样改变的是最后的赋值对象`,所以此时i=2,j=2。
例四:例四虽然有点小不同,但是其原理还是一样的,先入栈i再自增运算i++,此时栈中的i为,0,变量表中的i为1;然后自增运算++j,在入栈j,相加得出结果再保存回变量表,所以结果就是j=0+1,这里得出的j=1是相加运算得出的j=1,并不是自增指令得出的j=1,比如此时i为4的话,结果就是i=5,j=5。
4: iload_1
5: iinc 1, 1
8: iinc 2, 1
11: iload_2
12: iadd
13: istore_2