在Java中,运行以下代码:
1 int i=10; 2 i=i++; 3 System.out.println(i);
得到的结果仍然为10,为什么呢?理论上,运算的过程不应该是i首先把10取出来,赋值给i,然后i再自增1,结果不该是11吗?
原因还是要从反编译得到的汇编源码看起。在cmd窗口,输入命令javap -c Demo(Demo是class的文件名),可以得到反编译的汇编源码,
下面我们一步步来,先将两行简单的代码反编译一下。
1 int i=10; 2 int j=9;
上述两行代码的反编译结果是
1 0: bipush 10 //将常量10压入操作数栈 2 2: istore_1 //将操作数栈顶元素(10)弹出,存入局部变量位置1处 3 3: bipush 9 //将常量9压入操作数栈 4 5: istore_2 //将操作数栈顶元素(9)弹出,存入局部变量位置2处 5 6: return
上下代码可以很简单的对应起来,可以初步看出,赋值语句在汇编其实是有两步,第一步压栈(操作数栈),第二部(出栈并存储至局部变量处)。
为了进行验证,再多加一行代码,如下
1 int i=10; 2 int j=9; 3 j=i;
同样,反编译看汇编如下:
1 0: bipush 10 2 2: istore_1 3 3: bipush 9 4 5: istore_2 5 6: iload_1 6 7: istore_2 7 8: return
很明显的可以看到,j=i这一句,并不是直接把局部变量位置1的值赋值给局部变量2,而是先压入操作数栈,再弹出存储在局部变量位置2处!
下面我们把最开头的i=i++进行一下反编译,得到的结果如下。
1 public class Demo { 2 public Demo(); 3 Code: 4 0: aload_0 5 1: invokespecial #1 // Method java/lang/Object."<init>":()V 6 4: return 7 8 public static void main(java.lang.String[]); 9 Code: 10 0: bipush 10 //将常量10压入操作数栈 11 2: istore_1 //将操作数的栈顶元素(10)出栈,存入局部变量表1的位置处(这两句话完成了i=10的赋值操作) 12 3: iload_1 //将局部变量表1位置的值压入操作数栈 13 4: iinc 1, 1 //局部变量表1位置的值自增1 14 7: istore_1 //重新将操作数栈顶的值(10)出栈存入局部变量表1的位置处 15 8: return 16 }
只需要从第10行看起就可以了。
可以很明显的看到,自增过程是发生在压栈和出栈中间,所以最后出栈的值会把自增的结果覆盖,导致自增其实是没有作用的。
其实还可以得出一个结论,就是自增和普通的运算的步骤是不同的。自增是直接在局部变量区加1,而运算要进行压栈和出栈操作。
为了验证这句最关键的话,我们把下面代码进行一次反编译。
1 int i=10; 2 i++; 3 i=i+1;
得到的反编译结果如下:
1 0: bipush 10 2 2: istore_1 3 3: iinc 1, 1 4 6: iload_1 5 7: iconst_1 6 8: iadd 7 9: istore_1 8 10: return
这下很明显了,i++就只有一句话,iinc 1,1,代表在局部变量位置1处的值自增1,而i=i+1则是先把局部变量1位置处的值压栈,再把常量1压栈,再相加出栈,一共四句话!
因此,从内存的角度来看,i=i+1和i++是不同的,也就是说:
i=i++这句话与i=i=i++是不等价的!!!
明白了这个道理,我想再看这个题应该就没有那么恶心了!