public static void main(String[] args) {
int a = 10;
a = a++;
System.out.println(a);
System.out.println(a++);
System.out.println(++a);
}
输出结果相信各位客官都清楚。第一个输出为10、第二个输出为10、第三个输出为12
原因的话我想都能说出来,a++ 先使用后自增,++a 先自增后使用。至于第一个输出的结果如果不懂字节码指令的话会感觉到十分令人头大。
先来看看上诉方法的字节码指令:
0 bipush 10
2 istore_1
3 iload_1
4 iinc 1 by 1
7 istore_1
8 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
11 iload_1
12 iinc 1 by 1
15 invokevirtual #3 <java/io/PrintStream.println : (I)V>
18 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
21 iinc 1 by 1
24 iload_1
25 invokevirtual #3 <java/io/PrintStream.println : (I)V>
28 return
一脸懵逼?别慌我来慢慢详谈
即便不懂jvm内存结构也应该知道,jvm大概内存结构大概可以分为堆和栈两个结构。其中堆为线程共享内存结构,栈为线程私有内存结构,换句话说,一条线程对应一个栈结构。栈中存放的就是栈帧,所谓栈帧,也就是方法的调用。那么栈帧中又包含本地变量表、操作数栈、方法返回地址、动态链接以及一些其他附加信息。
就上述字节码指令来讲:
bipush 10 :将 10压入操作数栈
istore_1: 将10存放到本地变量表索引为1的位置
那么这两个指令执行完之后也就代表 int a = 10;这行代码执行结束
iload_1: 从本地变量表取出索引为1的值,在这里就是10
iinc 1 by 1: 自增1
istore_1: 存入本地变量表索引为1的位置
这里就是 a=a++;这行代码执行结束。那么为什么这里执行完之后a 不是11呢?
首先要搞明白iinc这个指令: Increment local variable by constant 这是官方给出的解释。大致的意思就是本地变量的的常量自增,那么就是说并不是压入操作数栈的数值自增。
从图上来看,第一幅图为第一行代码执行结束后的本地变量表,和操作数栈的状态。从第二幅图开始,首先 iload_1 将本地变量表索引为1的位置的值压入操作数栈,第三幅图执行iinc执行 将本地变量表索引为1的位置自增1,这个时候本地变量表中的10就成了11,然后第四幅图执行istore_1 将操作数栈中的值保存到本地变量索引为1的位置。这个时候操作数栈中的值为10,而且没有做过任何处理,自然将本地变量表中的值11覆盖为10了。
然后就是输出 a++的结果了
getstatic #2 在类中获取静态字段 #2 表示在运行时常量池中的位置
iload_1: 加载本地变量表中下标为1的位置的值压入操作数栈中。
iinc 1 by 1 本地变量表中下标为1的常量值自增1
invokevirtual #3 执行方法 #3 表示运行时常量池中的位置,总的来说就是执行print方法
调用print方法的时候传递的也是操作数栈中值,上诉指令是先将本地变量表中的值压入操作数栈,后将本地变量中的值自增1,那么这个时候操作栈中的是为10,传递给print方法的值自然也是10,所以输出的就是10
最后输出++a的结果
getstatic #2 与上诉相同
iinc 1 by 1 本地变量表中下标为1的常量值自增1
iload_1: 加载本地变量表中下标为1的位置的值压入操作数栈中。
invokevirtual #3 执行方法 #3 表示运行时常量池中的位置,总的来说就是执行print方法
与上边的解释相同,调用print方法的时候是将操作数栈中的值传递给print方法,但是这段指令是先进行的iinc 然后iload,也就是说在将本地变量表中值压入到栈之前先进行了自增,这个时候入栈的值就是12,输出的自然就是12
为什么是12? 前边不是有个a++?这段代码结束后本地变量表中的值为11,这个时候在自增不就是12了?如果将第二个输出注释掉,输出的就是11了。