问题:如何理解 i++ 和 ++i ?
该问题需要用到 Java 类在内存中的存储结构、栈帧、JVM 指令等底层内容。
简单理解,每个方法有独立的栈帧,每个栈帧有独立的内部结构,如下:
- LocalVariableTable(局部变量表,存放方法内的局部变量)
- OperandStack(操作数栈,存放方法内的操作数)
- DynamicLinking(动态链接,此处可简单理解为指针)
- ReturnAddress(返回值地址)
针对 i++ 和 ++i,有以下四种常见情况使用最简单代码描述
- i++ 无赋值
public static void main(String[] args) {
int i = 8;
i++;
System.out.println(i); // 9
}
其字节码如下:
bipush 8 // 将8入栈,即将8压入当前线程的操作数栈
istore_1 // 弹出操作数栈1号位置的数据,并放入局部变量表中,此时操作数栈为空,局部变量表有一个数据
iinc 1 by 1 // 对局部变量表1号位置的数据加一,此时局部变量表中的 i 为9,操作数栈为空
getstatic #2 <java/lang/System.out> // 获取静态变量 System.out
iload_1 // 获取局部变量表1号位置的数据,并压入操作数栈,此时局部变量表和操作数栈中的数据相同,即 i=9
invokevirtual #3 <java/io/PrintStream.println> // 执行 PrintStream.println 输出操作数栈的内容
return // 返回,此时无数据返回
- ++i 无赋值
public static void main(String[] args) {
int i = 8;
++i;
System.out.println(i); // 8
}
其字节码如下:
bipush 8 // 将8入栈,即将8压入当前线程的操作数栈
istore_1 // 弹出操作数栈1号位置的数据,并放入局部变量表中,此时操作数栈为空,局部变量表有一个数据
iinc 1 by 1 // 对局部变量表1号位置的数据加一,此时局部变量表中的 i 为9,操作数栈为空
getstatic #2 <java/lang/System.out>
iload_1 // 获取局部变量表1号位置的数据,并压入操作数栈,此时局部变量表和操作数栈中的数据相同,即 i=9
invokevirtual #3 <java/io/PrintStream.println>
return
- i++ 赋值给 i
public static void main(String[] args) {
int i = 8;
i = i++;
System.out.println(i); // 8
}
其字节码如下:
bipush 8 // 将8入栈,即将8压入当前线程的操作数栈
istore_1 // 弹出操作数栈1号位置的数据,并放入局部变量表中,此时操作数栈为空,局部变量表有一个数据
iload_1 // 获取局部变量表1号位置的数据,并压入操作数栈,此时局部变量表和操作数栈中的数据相同
iinc 1 by 1 // 对局部变量表1号位置的数据加一,此时局部变量表中的 i 为9,操作数栈中的 i 仍为 8
istore_1 // 弹出操作数栈1号位置的数据,并放入局部变量表中,即将操作数栈中的 i=8 覆盖局部变量表中的 i=9,最终操作数栈和局部变量表中的 i 都等于 8
getstatic #2 <java/lang/System.out>
iload_1 // 获取局部变量表1号位置的数据,并压入操作数栈,此时局部变量表和操作数栈中的数据相同,即 i=8
invokevirtual #3 <java/io/PrintStream.println>
return
- ++i 赋值给 i
public static void main(String[] args) {
int i = 8;
i = ++i;
System.out.println(i); // 9
}
其字节码如下:
bipush 8 // 将8入栈,即将8压入当前线程的操作数栈
istore_1 // 弹出操作数栈1号位置的数据,并放入局部变量表中,此时操作数栈为空,局部变量表有一个数据
iinc 1 by 1 // 对局部变量表1号位置的数据加一,此时局部变量表中的 i 为9,操作数栈为空
iload_1 // 获取局部变量表1号位置的数据,并压入操作数栈,此时局部变量表和操作数栈中的数据相同
istore_1 // 弹出操作数栈1号位置的数据,并放入局部变量表中,即将操作数栈中的 i=9 覆盖局部变量表中的 i=9,最终操作数栈和局部变量表中的 i 都等于 9
getstatic #2 <java/lang/System.out>
iload_1 // 获取局部变量表1号位置的数据,并压入操作数栈,此时局部变量表和操作数栈中的数据相同,即 i=9
invokevirtual #3 <java/io/PrintStream.println>
return
综上,通过对比以上四种情况,可以确定:
- 赋值操作永远是从操作数栈向局部变量表中赋值
- 读取操作永远是从局部变量表中获取变量值
- ++ 操作发生在局部变量表中,执行变量操作时才将变量从局部变量表压入操作数栈,执行赋值操作时将变量从操作数栈中取出并对局部变量表中的变量进行赋值
简单记忆(非复杂情况下):
- 单独执行 i++ 或 ++i 的效果相同,下一行命令中的 i 将为 i+1
- 执行
i=i++;
时,下一行命令中的 i 值不变 - 执行
i=++i;
时,下一行命令中的i=i+1