先来了解几个字节码指令
public static void main(String[] args) {
int a = 10;
int b = Short.MAX_VALUE + 1;
int c = a + b;
}
通过javap -v 来反编译上述的字节码文件,长这样:
这里locals大小也就是我们局部变量表的大小。
用图解的方式逐条分析:
- bipush 10:将一个btye数值压入操作数栈中;(其长度会补齐 4 个字节)
其它相似的指令:
- sipush:将short压入操作数栈,其长度会补齐 4 个字节;
- idc:将int压入操作数栈;
- idc2_w:将long压入操作数栈,分两次压入,因为long是8个字节;
- 当int为:-1~5时,采用iconst压入操作数栈。
-
istore_1:将操作数栈顶的数值弹出,保存到局部变量表为1的槽中;
-
idc #3:将常量池中#3的数据压入操作数栈;
低于short最大大小的数值会直接存放在方法区中,超过short最大大小的数值会被存放到运行时常量池中,如上图所示。
- istore_2:将操作数栈顶数据弹出到局部变量表为2的卡槽中;
- iload_1:将局部变量表卡槽1中的数据加载到操作数栈中;
- iload_2:同上;
- iadd:将操作数栈中的数值相加;
- istore_3:弹出栈顶数据到局部变量表中;
分析i++ 与 ++i
下面代码中,i和b的结果应该很容易猜出。
public static void main(String[] args) {
int i = 3;
int b = i ++ + ++i + i--;
// i = 4, b = 13
}
还是先来反编译看一下其字节码指令的执行流程:
想要理解i ++ 和 ++i,核心点就是 iload和iinc指令的执行顺序:
- i++:先load,再iinc;
- ++i:先innc,再load。
innc [局部变量表卡槽位置] [增加或减少的数值]
分析:
- iconst_3
- istore_1
- iload_1
- iinc 1,1:将当前局部变量表中卡槽为1的数值加1
注意,这里直接在局部变量表中做的自增操作,而我们操作数栈中的数值没有变化,还是3,上述的两步操作也就是i++的操作,先进行load操作,再innc自增。
- innc 1,1
- iload_1
上述两步对应的就是++i的步骤,再进行iinc操作,再load进操作数栈。
- iadd
- iload_1
- iinc 1,-1:将当前局部变量表卡槽为1的数据-1
- iadd
- istore_2
因为所以的运算操作都是再我们的操作数栈中完成的,而++操作都是直接再局部变量表中进行的,所以load、iinc的执行顺序的不同,就导致了i++和++i的的差异。
思考题
int i = 0;
for (int j = 0; j < 10; j++) {
i = i++;
}
System.out.println(i); // 0
- i++:先将i加载到操作数栈,此时i=0;随后在局部变量表中自增i,此时i在局部变量表中的值为1,操作数栈还是0;
- i = i++:将操作数栈顶的i弹出,保存到局部变量i的卡槽中,于是原本局部变量表中i的值为2,但在这一步,又被赋值为0了。
- 循环这种操作,最后i=0。
++i 结果就不一样了
int i = 0;
for (int j = 0; j < 10; j++) {
i = ++i;
}
System.out.println(i); // 10