前言
最近在看JVM的视频,发现了一个很有意思的知识点,让我感觉我三年编程白学了。话不多说,上代码:
public class Main {
public static void main(String[] args) {
int i = 1;
i = i++ ;
int j = i++;
int k = i + ++i * i++;
System.out.println(i);
System.out.println(j);
System.out.println(k);
}
}
根据我的经验,i++是先取值再自增,++i是先自增再取值,所以 i = i++
的结果应该是i=2
;j=i++
的结果应该是i=3;j=2
;最后k的结果应该是3 + 4 * 3 = 15
。那么究竟对不对呢?我们运行一下程序,查看结果:
是不是发现自己一个都没对呢?那我们来看看为什么会这样。
了解原因之前,我们需要了解两个知识点
局部变量表和操作数栈
局部变量表
局部变量表是在Java虚拟机中用来存放函数参数以及实际变量信息的一段内存,例如当我们定义了i=1
时,Java虚拟机就会在局部变量表中划分一段内存存放i=1这个结果,当方法执行完毕后,随着函数栈帧的销毁,局部变量表也随之销毁释放空间。
操作数栈
首先我们要了解什么是栈?学过数据结构的朋友们应该会很清楚:栈是一个线性表,它用来存放基本类型变量和对象的引用(这里强调引用两个字,因为对象本身是存放在堆中)。
那么操作数栈,顾名思义,就是用来存储程序运行过程中产生的操作数的,不过这些操作数都是临时使用的,就好像我们在进行计算时所使用的草稿纸一样,在计算完之后这些操作数就会被销毁。
程序运行时的局部变量表和操作数栈
在程序运行时,当我们定义一个变量时,若没有进行计算,则Java虚拟机会将变量直接放在局部表空间中。若在定义了变量时进行了算式运算,则会将参与运算的数放在操作数栈中进行临时存储。
例如:
int i = 1;
int j = i + 1;
在上面这段代码中,在进行i=1时,没有进行变量算式运算,不会产生临时的数据,因此将 i = 1放在局部表空间中存储。
在计算j = i + 1
时,涉及到了变量算式运算,因此在这段代码的执行过程中,先进行等式右边的计算,首先将1(i)压入操作数栈,再进行 1 + 1的操作,再将计算的结果:2 压入操作数栈,最后将操作数栈的2赋值给 i ,将i=2放入局部变量表中进行存储。
代码解析
在上面的代码中,我们一步一步的进行解析
i = i++;
在这段代码中,先进行等式右边的计算:i++,在这个过程中,首先取值 i = 1,将1推入操作数栈,之后进行 i = i+1的计算,此时将i = 2 放入局部变量中,此时的局部变量中 i = 2,操作数栈中为 1 ,最后进行赋值运算,将操作数栈中的1赋值给i,最终得到i=1。
j = i++;
在这段代码中,先进行等式右边的计算:i ++,在这个过程中,首先取值 i = 1,将1推入操作数栈中,再进行 i = i +1的运算,此时将 i = 2 放入局部变量中,在进行赋值运算,将操作数栈中的1赋值给j,此时j = 1,i = 2。
k = i + i * i;
这段代码比较复杂,但仍然先进性右边的计算。在这里我们需要注意的是,即使乘法的优先级大于加法,但是在JVM中,仍然是从左往右进行操作数栈的入栈操作。
- 首先,将i = 2压入操作数栈中,此时i = 2,操作数栈01为 2。
- 再进行i的入栈操作,在i中,我们先进行i+1的操作,取得3压入操作数栈中,此时 i = 3,操作数栈02中为 3。
- 接着进行 i++的入栈操作,先取值i=3,将3压入操作数栈中,再进行i+1的操作,此时 i = 4,操作数栈03中为3。
- 最后进行算式运算,为:2 + 3 * 3 = 11,再将11赋值给k。
最终结果
经过上面的运算,此时i = 4,j = 1,k = 11。这样,我们就成功的计算出了正确的结果。
总结
- 先进行等式右边的运算,赋值操作最后运行
- 等式右边的计算中,依次从左往右压入操作数栈
- 算式运算的过程中依照实际运算优先级进行计算,先乘除后加减
- i和i中的自加自减操作时都是之间修改局部变量表中的值