前些天看到一篇关于“毒代码”的文章(https://www.jianshu.com/p/3d7dfbfec566),文章的内容还不错,不过有一个问题始终觉得解释的不够透彻,问题如下
关于result = num++和result = ++num的问题,我想早在大学时,老师已经都讲臭了,前置++先自加后赋值,后置++先赋值后自增。
所以当笔者第一眼看到上面那个问题的时候,第一反应是输出99,因为很自然的会觉得 num = num++ 会被分解为:
1:num = num;
2:num++;两步
也就是说将0赋值给num,然后num自增为1,如此循环,但是事实却不是这样,上面的结果为0!
我们注意这里两个表达式的区别 result = num++ 和 num = num++ 这两种写法可是完全不一样的,至于怎么不一样继续往下看。
看了文章中的解释,感觉也是云里雾里,为什么num++没起作用?为什么++后num值没变?难道上学的时候老师说的是错的?
带着这些问题,我们来看下生成的字节码,分析一下,前置++和后置++过程中,JVM究竟做了什么。
首先java代码
public class TestMainClass1 {
public static void main(String[] args) {
int num=0;
num = num++;
System.out.println(num);
}
}
public class TestMainClass1 {
public static void main(String[] args) {
int num=0;
num = ++num;
System.out.println(num);
}
}
javac生成字节码后,通过javap -c 查看
public class com.aurora.test.TestMainClass1 {
public static void main(java.lang.String[]);
Code:
0: iconst_0 // 整数0入栈
1: istore_1 // 将0弹出栈,并存储到局部变量1
2: iload_1 // 将局部变量1的值0入栈
3: iinc 1, 1 // 局部变量1增加1,值变为1,不操作栈
6: istore_1 // 0出栈,并存储到局部变量1
7: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
10: iload_1
11: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
14: return
}
public class com.aurora.test.TestMainClass1 {
public static void main(java.lang.String[]);
Code:
0: iconst_0 // 整数0入栈
1: istore_1 // 将0弹出栈,并存储到局部变量1
2: iinc 1, 1 // 局部变量1增加1,值变为1,不操作栈
5: iload_1 // 将局部变量1的值1入栈
6: istore_1 // 1出栈,值存储到局部变量1
7: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
10: iload_1
11: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
14: return
}
可以看到区别就是iload和iinc这两个操作顺序的问题,
后置++在iinc增加局部变量的值后,又从操作栈中将变量值istore回了局部变量中,等于iinc白加了。
而前置++是先修改局部变量值,然后局部变量入栈,最后出栈存入局部变量,这一套下来,局部变量的值是变了的。
单纯的文字可能还不太好理解,这里让我这个灵魂画手用图表示下(非战斗人员请迅速撤离):
前置++我就不画了(我画的闹眼睛),本质就是先iinc自增,然后将自增后的值入栈,然后再出栈存入局部变量表。
那么再回到 result = num++ 和 num = num++ 的问题,第一种是赋值给了新变量,第二种是赋值给了num,也就是图中画的那种操作。赋给新变量后,num不会被覆盖,而赋值给num自身的话,因为num自增后结果被出栈的值0给覆盖了,所以num最后还是0.
下面还有几篇文章,也从不同角度讲了这个问题,可以参考看下
2、https://www.cnblogs.com/thiaoqueen/p/8466359.html