Java变量自增表达式 i = i++ 的底层逻辑(简述)
Java变量自增表达式 i = i++ 的底层逻辑(简述)
前言
很多老师告诉我们,i = i++ 的运算过程是 temp = i; i ++; i = temp; 所以i的值不变。但我总觉得这个temp的出现有些莫名其妙。所以在网上检索之后,把大佬们的解释做了一点总结和简化,权当拾人牙慧。
要搞懂 i = i++ 我们先要简单认识两个东西:局部变量表 和 操作数栈。
- 操作数栈:一个临时的存储空间, 主要用于保存计算过程的变量和中间结果,
- 局部变量表:也是一个临时的存储空间,它用于保存函数的参数以及局部变量。
- 对于本文章而言,初学者可以把它看做两个桶:一个用来装i的值(局部变量表),另一个用来装中间过程中用到的数(操作数栈)
正文
了解了以上内容,就可以对i=i++进行理解了。不过在此之前,我们先看看单独的i++ 和 ++i的区别:
i++ 和 ++i
i++ 和 ++i,它们实际上是直接在局部变量表里修改变量的值,原地修改,不需要经过操作数栈。所以,作为语句单独使用时,没有区别。
public static void main(String[] args) {
int i = 0;
i++; // 在局部变量表直接自增
++i; // 在局部变量表直接自增,没有区别
}
那么当情况是 i = i++时; 为什么结果就是i的值不变呢?
i = i++
public static void main(String[] args) {
int i = 0;
i = i++; // i值不变
}
这就是操作数栈参与的结果,上面代码的执行过程,实际上是这样的:
- 执行 int i = 0;
- 把0这个常数放到操作数栈中
- 从操作数栈顶取出常数0,然后存储到局部变量表的索引为1的位置(局部变量表[1]),这个位置就代表i的值(因为局部变量表里有args 和 i 两个元素,args的索引是0)。
- 执行 i = i++;
- 计算机首先看见右侧表达式中的i, 所以它把局部变量表[1]的值取出,压入操作数栈。
- 计算机又看见了符号“++”,于是把局部变量表[1]进行自增
- 然后计算机看见 “=” ,所以对等号左边的i进行赋值,重点来了:
- 这里赋的是哪个值呢? -- 操作数栈里的值,0。
- 那么赋值到哪里呢? -- 局部变量表[1]。直接覆盖了自增的结果,也就是说,刚刚的自增操作,增了个寂寞。
- 所以我们就知道了:由于刚刚是“先压栈,再自增”,所以栈里的值还是原始值,最后又覆盖回去了。
- 同理,我们也就知道它和 i = ++i 的不同之处在哪里了
- 计算机这次首先看见的是“++”符号,而不是i, 所以它这次先把局部变量表[1]进行自增
- 然后计算机才看见了i,此时才把局部变量表[1]的值取出,压入操作数栈,因此,栈里的值也变成了1,最后覆盖回去就是1。
以上顺序的变化,实际上在JVM编译后的字节码文件中能够直观地看到,但是初学者对字节码很陌生,所以采用了以上的描述方式。字节码的区别其实更加直观,如下:
- i = i++ 的执行顺序:
iconst_0 # 把int常量0压入操作数栈
istore_1 # 把操作数栈顶的值"0"存储到局部变量表[1]
iload_1 # 局部变量表[1]的值压入操作数栈顶,此时操作数栈顶为0
iinc 1 1 # 将局部变量表[1]的值,加上1, 所以此时i的值变成1
istore_1 # 将操作数栈顶的值存储到局部变量表[1], 用0覆盖了1.
- i = ++i 的执行顺序:
iconst_0 # 同上
istore_1 # 同上
iinc 1 1 # 将局部变量表[1]的值,加上1, 此时i的值变成1
iload_1 # 局部变量表[1]的值压入操作数栈顶,此时操作数栈顶为1
istore_1 # 将操作数栈顶的值存储到局部变量表[1], 用1覆盖了1.
这个问题的进阶还有 k = i + ++i * i++ 参考文章:https://blog.csdn.net/See_Star/article/details/125206538
作者: 练块儿的程序员
出处:https://www.cnblogs.com/sunyujun16
本文版权归作者和博客园共有,欢迎转载,但必须保留此段声明,且在文章页面明显位置给出原文链接, 如有问题,可邮件[email protected]咨询.