文章目录
- 问题描述
- 模拟++的机制
- 源码解析
- 指令图解
- 1.方法执行前在内存中的的数据结构
- 2.执行iconst_0
- 3.执行istore_1
- 4.执行iload_1
- 5.执行iinc 1 by 1
- 6.执行istore_1
- 测试示例
- 结论
问题描述
-
记得刚开始学编程的时候还是从c语言开始的,还是看的谭浩强写的那本书,上面对介绍i++和++i的区别如下:
i++是先赋值,然后再自增;++i是先自增,后赋值。
用代码表示就是:
若 a = i++; 则等价于 a=i;i=i+1;
而 a = ++i; 则等价于 i=i+1;a=i; -
那么事实真是这样么,只是曾经我深信不疑,但是直到我看到下面这段代码:
@Testpublic void test(){int i = 0;i=i++;System.out.println(i);}
如果按原先定义,就应该是i=i;i=i+1; 那么结果就应该是1;但是很遗憾结果是0;所以得知原先定义有误,至少是不准确的。
模拟++的机制
那么真实的机制是怎么样的呢?我简单用代码模拟一下它的效果:
int i;@Testpublic void testAddI() {i = 0;i = lastAdd();System.out.println(i);i = 0;i = firstAdd();System.out.println(i);}//模拟i++的机制public int lastAdd() {//操作i前对其值保留副本int temp = i;i = i + 1;//返回副本return temp;}//模拟++i的机制public int firstAdd() {i = i + 1;return i;}
输出结果为0和1,和i=i++以及i=++i的结果一致。
通过以上代码模拟,似乎在java的执行过程中,i++和++i都直接对i进行了i=i+1的操作,但是不同的是i++得到的是i未进行加法操作的前的值的副本,而++i直接得到计算后的值。那么,事实真的是这样吗,我们再去刨析一下源码,看看在汇编指令中,它到底是怎么做的。
源码解析
再写一个类,源码如下:
public class PlusI {public void iPlusPlus(){int i = 0;i++;}public void plusPlusI(){int i = 0;++i;}}
对其class文件进行反汇编后,代码如下:
public class com.aliencat.javabase.bit.PlusI {public com.aliencat.javabase.bit.PlusI();Code:0: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnpublic void iPlusPlus();Code:0 iconst_01 istore_12 iinc 1 by 15 returnpublic void plusPlusI();Code:0 iconst_01 istore_12 iinc 1 by 15 return}
先不谈这些汇编指令的意义,乍一看,两个方法的执行指令完全一样。
我们再把代码改下,看看为什么i=i++和i=++i会产生不一样的结果:
public class PlusI {public void iPlusPlus(){int i = 0;i = i++;}public void plusPlusI(){int i = 0;i = ++i;}
}
对其进行反汇编后,代码如下:
public class com.aliencat.javabase.bit.PlusI {public com.aliencat.javabase.bit.PlusI();Code:0: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnpublic void iPlusPlus();Code:0 iconst_01 istore_12 iload_13 iinc 1 by 16 istore_17 returnpublic void plusPlusI();Code:0 iconst_01 istore_12 iinc 1 by 15 iload_16 istore_17 return}
关于汇编指令的解析请参考:通过jvm指令手册看懂java反汇编源码
关于方法在JVM的内存模型请参考:一文看懂Java内存模型(JMM)
通过比较我们发现,除了iload_1
和iinc 1 by 1
这两条指令顺序有区别外,其它都是一致的。
下面我们来分析一下每条汇编指令的意义:
-
iconst_0:将int类型的0值压入操作数栈
-
istore_1: 弹出操作数栈顶的值赋给局部变量表下标为1的变量
-
iload_1: 将局部变量表下标为1的位置存储的值压入操作数栈
-
iinc 1 by 1:取局部变量表下标为1的位置存储的值加上1
-
istore_1:弹出操作数栈顶的值赋给局部变量表下标为1的变量
指令图解
下面是关于i=i++;
执行过程的图解:
1.方法执行前在内存中的的数据结构
因为实例方法的局部变量表中默认第一个是保存的this,所以i的下标位置为1
2.执行iconst_0
将int类型的0值压入操作数栈
3.执行istore_1
弹出操作数栈顶的值赋给局部变量表下标为1的变量
4.执行iload_1
将局部变量表下标为1的位置存储的值压入操作数栈
5.执行iinc 1 by 1
取局部变量表下标为1的位置存储的值加上1(指令中第一个1代表局部变量表的下标)
6.执行istore_1
弹出操作数栈顶的值赋给局部变量表下标为1的变量
下面是关于i=++i;的图解,我就不一一解释了
测试示例
如果弄懂了上面的原理,很容易猜出下面的计算结果
public static void main(String[] args) {int i = 0;System.out.println(i++); //输出0i = 0;System.out.println(i++ + i++);//输出 1i = 0;System.out.println(i++ + ++i); //输出2i = 0;System.out.println(i++ + i++ + i++); //输出3i = 0;System.out.println(i++ + i++ + i++ + i++); //输出6//明明只有4个i+1为什么却得出6的结果你能从你原理解的定义推理出来吗?}
结论
- 在使用i=i++的过程中,它会先把i的原始值0复制到操作数栈中,然后再对局部变量表中的0进行+1操作使得i变为了1,此时操作数栈顶的值为0,然后执行赋值操作时候使用的是弹出的操作数栈顶的值,所以最后i又被修改为了0;
- 而i=++i的过程则是先对局部变量表中i的原始值进行加1的操作,即使得i由0变为1,然后将i的值复制到操作数栈,最后赋值即弹出操作数栈顶的值。
- i++;和++i;的执行过程和结果是一样的。
- 在使用i++和++i赋值的过程中,他们区别在于前者先复制当前数据,再进行原始值加1的操作,后者则先进行了原始值加1的操作,再对计算后的结果进行了复制,最后返回的其实都是放入操作数栈的拷贝。
- 看懂了上面的原理,你应该能明白为什么int i = 0;i=i++ + ++i;等于2了吧。如果按原来的定义取理解,也许会得出结果为1。