我们都知道 i++ 与 ++i 都是自增操作。大多数也知道两种 "先加" 和 "后加的区别"。例如下方代码:
public class Hello{
public static void main(String[] args) {
int i = 0;
System.out.println(i++);
int j = 0;
System.out.println(++j);
}
}
打印出
i = 0
j = 1
可以看到 j
完成了自增,并且打印出 1
。可是 i
确依据打印出 0
。那么 i
是不是没有完成自增呢?改动一下上方代码:
public class Hello{
public static void main(String[] args) {
int i = 0;
System.out.println("i = " + i++);
int j = 0;
System.out.println("j = " + ++j);
System.out.println("i = " + i);
}
}
打印出
i = 0
j = 1
i = 1
可以看到第二次对 i
进行读取的时候就打印出了自增后的值。说明 i++
的值晚一步来到了可这是为什么呢?
从 JVM 角度分析 i++
和 ++i
的问题。
我们先简化一下上方的代码,只用于研究自增问题简化代码如下:
public class Hello{
public static void main(String[] args) {
int i = 0;
System.out.println(i++);
System.out.println(++i);
}
}
我们在 CMD
控制台中使用 javac Hello.java
编译上方代码。
再使用 javap -c Hello.class
查看编译过后字节码。
控制台打印出:
Compiled from "Hello.java"
public class Hello {
public Hello();
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: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
5: iload_1
6: iinc 1, 1
9: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
12: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
15: iinc 1, 1
18: iload_1
19: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
22: return
}
我们需要观察 main
方法总的字节码就行了。再此之前我想先解释下 "局部变量表" 和 "操作数栈" 。
局部变量表:顾名思义,存放方法中局部变量的地方。是一个数组。
操作数栈:大多数的对数栈的操作都要先从局部变量表中拿出值放在数栈中,在从数栈中取值进行操作。是一个先进后出的队列。
好了,我们在来解释 main
方法中的字节码。
public static void main(java.lang.String[]);
Code:
// 将 i 的值从常量表中取出,放到操作数栈中。
0: iconst_0
// 从数栈中取出值放到局部变量表中的第 1 位。第 0 位已经被 `java.lang.String[]`占用
1: istore_1
// 简单理解为初始化 System.out.println()
2: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
// 以下是 i++ 操作
// 从局部变量中取出第一个值,放入数栈中
5: iload_1
// 局部变量表中第一位值自增 1
6: iinc 1, 1
// 从操作数栈中取出值,并打印出来。
9: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
// 以下是 ++i 的操作,你可以对比上方解释自己尝试翻译下。
12: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
15: iinc 1, 1
18: iload_1
19: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
22: return
}