看字节码之前需要先了解相关概念,如栈帧、操作数栈、局部变量表。
栈帧是JVM中很重要的一个概念,因为JVM是基于栈的架构。一个方法的调用其实就是栈帧入栈出栈的过程。栈顶栈帧就是当前方法调用。
一个栈帧中包含:
- 局部变量表
- 操作数栈
- 动态链接
- 方法返回地址
这里i++、 ++i涉及到的就是局部变量表和操作数栈。具体信息可参考:《Java虚拟机规范》
局部变量表存储的是方法的参数以及内部定义的变量的值,操作数栈也是一个栈结构,用来执行方法中的指令。
好了,来看一个代码片段:
class Scratch {
public static void main(String[] args) {
int i=0,j=0,m=0;
j = i++;
m = ++i;
}
}
为了不产生其他多过信息,这里只写了关键代码。
首先可以通过javac
将源码编译成class:
javac scratch.java
执行完成后将看到Scratch.class 文件,通过javap命令查看字节码:
javap -c Scratch > scratch.txt
为了方便查看将结果输出到了scratch.txt,打开此文件将看到如下信息:
Compiled from "scratch.java"
class Scratch {
Scratch();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: iconst_0
1: istore_1
2: iconst_0
3: istore_2
4: iconst_0
5: istore_3
6: iload_1
7: iinc 1, 1
10: istore_2
11: iinc 1, 1
14: iload_1
15: istore_3
16: return
}
关注main方法 0-5,可以发现对应的是:
int i=0,j=0,m=0;
主要是声明变量并初始化为0。我们可以发现下划线后面跟了一个数字,这里应该代表的是变量在局部变量表中的位置。
i:1
j:2
m:3
再关注main方法:6、7、10:
iload_1
表示将局部变量表中位置1的数据放入操作数栈中(这里对应的是i,此时i的值为0)然后pop出来赋值给j。然后再将i自增iinc
。最后istore_2
存储j到局部变量表中。
操作完成后i=1,j=1。
最后关注main方法:11、14、15:
iinc
首先自增i,然后iload_1
将局部变量表中位置1的数据放入操作数栈中(这里对应的是i,此时i的值为2)然后pop出来赋值给m。最后istore_3
存储m到局部变量表中。
操作完成后i=2,j=1,m=2。
这就是为什么大家都说,i++是先赋值后自增,而++i是先自增后赋值的原因。
这里要说明的是,如果是单独的i++、++i是没有什么区别的。