前情提要
前置知识
- Java运行时数据区中的虚拟机栈(Java Virtual Machine Stacks)
- 栈帧(Frame)
- Local Variables(局部变量表)
- Operand Stack(操作数栈)
- 栈帧(Frame)
先看一道题
public static void main(String[] args) {
int i = 8;
i = i++; // 输出8
//i = ++i; // 输出9
System.out.println(i);
}
我们都知道 i++ 是先赋值在运算 所以输出8。++i 先运算在赋值 所以输出9。
但是你知道在字节码层面是如何实现的么?
使用 idea 插件 jclasslib 查看字节码文件
0 bipush 8
2 istore_1
3 iload_1
4 iinc 1 by 1
7 istore_1
8 getstatic #2 <java/lang/System.out>
11 iload_1
12 invokevirtual #3 <java/io/PrintStream.println>
15 return
看不懂没事,我们先来看看上面几个指令是什么意思
指令
- bipush
将一个byte带符号扩展为int,然后压入操作数栈。简称压栈。 - istore
弹出操作数栈栈顶的数字,放到局部变量表下标为n的位置 - iload_< n >
分析i++字节码文件
局部变量表
0 bipush 8 // 把 8 压栈
2 istore_1 // 把 8 弹出,放入局部变量表中下标为 1 的位置
3 iload_1 // 把局部变量表中下标为 1 的数压栈,即把 8 压栈
4 iinc 1 by 1 // 把局部变量表下标为 1 的数加 1。注意,此时局部变量表中 i 的值等于 9 了。栈中的 i 还是 8
7 istore_1 //把 8 弹出,放入局部变量表中下标为 1 的位置。注意,此时局部变量表中i的值又从 9 变回 8 了。
8 getstatic #2 <java/lang/System.out>
11 iload_1
12 invokevirtual #3 <java/io/PrintStream.println>
15 return
int i = 8 相当于以下两条指令:
0 bipush 8
2 istore_1
i = i++ 相当于以下三条指令:
iload_1
iinc 1 by 1
istore_1
所以 i++ 不管多少次,最后输出的都是 8
public class TestIPulsPlus {
public static void main(String[] args) {
int i = 8;
for (int j = 0; j < 10; j++) {
i = i++;
}
// i = ++i;
System.out.println(i);
}
}
分析++i字节码文件
java代码:
public class TestIPulsPlus {
public static void main(String[] args) {
int i = 8;
i = ++i;
System.out.println(i);
}
}
使用 idea 插件 jclasslib 查看字节码文件
0 bipush 8
2 istore_1
3 iinc 1 by 1
6 iload_1
7 istore_1
8 getstatic #2 <java/lang/System.out>
11 iload_1
12 invokevirtual #3 <java/io/PrintStream.println>
15 return
把8压栈,弹出,放到局部变量表,8加1变成9,把9压栈,把9弹出放到局部变量表。
总结
自增操作是在局部变量表中进行的。
参考资料
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.bipush
思考题
下面我们再做一道题来巩固一下
public static void main(String[] args) {
int i = 1;
for (int j = 0; j < 10; j++) {
i = i++;
}
int k = i++;
int m = i + ++i * i++;
System.out.println("i = " + i);
System.out.println("k = " + k);
System.out.println("m = " + m);
}
请问 i、k、m 依次输出多少?
i = 4
k = 1
m = 11