大家都知道i++跟++i的区别:
- i++是先赋值再运算
- ++i是先运算再赋值
那可能很多人没有写过i=i++
或者i=++i
,这样的骚语句,这个时候是什么样的情况呢?
可能很多人能想到i=i++
,结果i=0,因为先赋值,这时候值是0,所以i=0。
而i=++i
最终结果i=1,因为是先运算+1再赋值,这时候已经是1了。
那到底是做了什么样的操作来实现这样的结果的呢?我们来追根溯源,正常情况,我们应该先看一下class文件显示什么东西,那我们写了几个最简单的方法如下:
public class TestI {
public void testMethod() {
int i = 0;
i = i + 2;
}
public void testMethodA() {
int i = 0;
i = i++;
}
public void testMethodB() {
int i = 0;
i = ++i;
}
}
这里为了明白正常的i+1
在java中是怎么处理的,增加了一个i=i+2
的对照组。
我们用javac TestI.java
编译成class文件,如下:
public void testMethod() {
byte var1 = 0;
int var2 = var1 + 2;
}
public void testMethodA() {
byte var1 = 0;
int var2 = var1 + 1;
}
public void testMethodB() {
byte var1 = 0;
int var2 = var1 + 1;
}
testMethodA
竟然跟testMethodB
是一样的。Emmm,因吹斯听,应该是IDE在翻译字节码的时候没有看出来这俩的区别?那我们还是来看字节码好了。
用javap -c TestI.class
命令查看字节码如下:
这里需要的知识储备是JVM指令集和JVM 栈帧之操作数栈与局部变量表,默认读者了解不在赘述。
public class testJava.TestI {
public testJava.TestI();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public void testMethod();
Code:
0: iconst_0 //将int型0压栈至栈顶
1: istore_1 //将栈顶int型数值存入第1个本地变量
2: iload_1 //将第1个int型本地变量压栈至栈顶
3: iconst_2 //将int型2压栈至栈顶
4: iadd //栈顶两个数相加并进栈
5: istore_1 //将栈顶int型数值存入第1个本地变量
6: return //从当前方法返回void
public void testMethodA();
Code:
0: iconst_0 //将int型0压栈至栈顶
1: istore_1 //将栈顶int型数值存入第1个本地变量
2: iload_1 //将第1个int型本地变量压栈至栈顶
3: iinc 1, 1 //将指定int型变量增加指定值,可以有两个变量,分别表示index, const,index指第index个int型本地变量,const增加的值,所以是第1个本地变量增加1
6: istore_1 //将栈顶int型数值存入第1个本地变量
7: return //从当前方法返回void
public void testMethodB();
Code:
0: iconst_0 //将int型0压栈至栈顶
1: istore_1 //将栈顶int型数值存入第1个本地变量
2: iinc 1, 1 //将指定int型变量增加指定值,第1个本地变量增加1
5: iload_1 //将第1个int型本地变量压栈至栈顶
6: istore_1 //将栈顶int型数值存入第1个本地变量
7: return //从当前方法返回void
}
我们做了一个最简单的i=i+2
的对照组来看正常情况下这个最简单的增量操作是长什么样子的,它的具体流程是先把i在本地变量表里面初始化出来,再把i的值放到操作数栈,再给操作数栈放一个需要加的2,然后i跟2相加,得到的结果再存到本地变量完成相加操作。
而i = i++或者i = ++i的操作还是跟i=i+1有很大区别的。最大的区别就是i=i+2
是用了iadd,而i++是用了iinc,iadd是作用在操作数栈中的,而iinc是直接在本地变量表中直接把变量增加。
来看i++
和++i
,其实就是iinc
跟iload
的运行顺序的区别,印证了我们之前所说的这两者的区别,我们再赘述一遍两者的区别,看是怎么体现出来的:
- i++是先赋值再运算
- ++i是先运算再赋值
i++是先iload_1
把0这个值推到操作数帧顶部,再iinc
把本地变量表里面的i做+1操作,这个操作结束后意味着这时候在操作数栈里面代表i的值仍然是+1之前的值也就是0,而其实本地变量表中的值已经是1,但是得下一个再iload1
的时候才是1代表i出战。
而++i,正好相反是先iinc
做+1操作,然后再代表i出证,这时候的值已经变成了1。
所以后面执行到i=
i++还是i=
++i的共同代码i=
,在字节码中也就是istore_1,把这时候栈中代表i出征的值赋值回本地变量表中的i,所以这时候i=i++操作数栈里面的0覆盖了本地变量表中的1。
所以i=i++比i++做得多此一举事情就是多做了一个i=, 把操作数栈中的0覆盖了本地变量表中的正确值1.
说一千道一万,什么都比不上一张图来的直观:
那既然i++是先赋值再运算,那我们多做几次i=i++是不是就好了,比如加个while循环,like below:
public void testMethodC() {
int i = 0;
for(int j = 0;j<100;j++){
i = i++;
}
}
public void testMethodC();
Code:
0: iconst_0 //将int型0压栈至栈顶
1: istore_1 //将栈顶int型数值存入第1个本地变量
2: iconst_0 //将int型0压栈至栈顶
3: istore_2 //将栈顶int型数值存入第2个本地变量
4: iload_2 //将第2个int型本地变量推送至栈顶
5: bipush 100 //将将单字节的常量值100推送至栈顶
7: if_icmpge 21 //栈顶弹出两个值,比较两int型数值大小,当结果大于等于0时跳转到21
10: iload_1 //将第1个int型本地变量推送至栈顶
11: iinc 1, 1 //将指定int型变量增加指定值,第1个本地变量增加1
14: istore_1 //将栈顶int型数值存入第1个本地变量
15: iinc 2, 1 //将指定int型变量增加指定值,第2个本地变量增加1
18: goto 4 //跳转到偏移位4
21: return
其实想一想还是0哈,因为每次的i都是重新load到操作数栈的,之前的+1过来的又会被覆盖,不管循环多少次都是一样的: