结论
先上代码和结论:
public static void main(String[] args) {
int i = 0;
int x = 0;
while (i < 10){
x = x++;
i++;
}
System.out.println(i); // 10
System.out.println(x); // 0
}
}
结论:
x++是先返回值再进行计算,由于语句还没结束,当前返回值是0,然后将0返回赋值给x,最后x==0;
分析
我们这里根据字节码操作指令进行分析,如下是反编译后的文件
这里0-3行是给变量i和x赋值0的操作
第四行iload_1的操作是将栈帧中局部变量表中第一个槽位中的值压入操作数栈中;
虚拟机栈:
Java 虚拟机栈:Java Virtual Machine Stacks,每个线程运行时所需要的内存
每个方法被执行时,都会在虚拟机栈中创建一个栈帧 stack frame(一个方法一个栈帧)
Java 虚拟机规范允许 Java 栈的大小是动态的或者是固定不变的
虚拟机栈是每个线程私有的,每个线程只能有一个活动栈帧,对应方法调用到执行完成的整个过程
每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存,每个栈帧中存储着:
局部变量表:存储方法里的 Java 基本数据类型以及对象的引用
动态链接:也叫指向运行时常量池的方法引用
方法返回地址:方法正常退出或者异常退出的定义
操作数栈或表达式栈和其他一些附加信息
这里我们画图理解一下
- 字节码文件中第四行的操作如上所示,iload_1就是将一号槽位的值压入栈中
- 第五行 bipush jvm讲一个整形常量压入栈中,也就是判断条件中的i<10中的10压入操作数栈中
- 第七行中if_icmpge的作用是判断 i < 10 中如果 i > 10 ,则跳转到第21行
21 - 32 行输出i和x的值
System.out.println(i); // 10
System.out.println(x); // 0
- 第十行将局部变量表中槽位2的值压入栈中,也就是x = 0;
- 十一行iinc的作用是自增,后面的 2 1 是指将2号槽位的值自增1
- 十四行则是将栈顶层的元素弹出,返回给操作数栈中的2号元素
- 十八行goto的作用是跳转 , 4 是跳转到第四行
为什么 x 自增后需要弹出栈而 i 自增后不需要弹出栈?
因为java源代码中 x = x++;i++;x是自增后又做了一遍赋值操作,所以需要将栈中的值弹出返回给局部变量表中对应槽位进行赋值。!!!但是x = x++;运算结果为0的根本原因也是出自于此,在第一次循环时,x 为 0,x ++是先赋值后运算,先将 0 压入栈中,然后 i ++ 进行自增操作 ,局部变量表中的值自增为1 ,最后由于 x = x++;又做了一遍将x的值赋值给x的操作,也就是将操作数栈中的x值弹出赋值给局部变量表中,此时操作数栈中x值为0,赋值给局部变量表中,x从原先自增的1 又变成了 0 ,循环十次之后,输出结果还是0;
通过画图理解一下: