引入
我們先看一個例子:
int i = 0 ;
int j = i ++ ;
System.out.println( "i = " + i + ","+ "j = " + j);
int x = 0 ;
int y = ++ x ;
System.out.println( "x = " + x + ","+ "y = " + y);
這個例子,輸出的是什麼呢? 有的人會覺得很簡單,也有的人會覺得,有點蒙的樣子。我們之前一般喜歡說:
- i++ , 先賦值在做 ++ 運算;
- ++i ,先做 ++ 運算,再進行賦值。
那根據這個來,我們這裏的 j 的值應該是 0 , i 的值應該是 1 。x 的值應該是 1 , y 的值是 1 。我們的程序跑一下就知道了:
i = 1,j = 0
x = 1,y = 1
方式一:口訣
我之所以說這個第一個個方式是口訣,是因爲它的確只需要記住這句話就好了:
- i++ , 先賦值在做 ++ 運算;
- ++i ,先做 ++ 運算,再進行賦值。
這種方式應該是一種很常見,也很高效的做法。我們通過前面的例子,也可以看到一個結論就是: i 最後一定做了 ++ 運算。這也就是爲什麼我們在 for 循環裏面寫 ++i , i++ 爲什麼問題不大的原因。
剛剛也說了,這種方法很常見,也很高效,**但是,它就是最準確的嗎?**這麼來說,它可以解決目前我們見到的 99% 的場景,那剩下的 1% 的場景怎麼辦呢?我們舉個例子:
int a= 0 ;
a = a ++ ;
System.out.println( "a = " + a );
根據我們之前的口訣, a 會先賦值給 a , 在做 ++ 運算。也就是說, a 一定做了 ++ 運算。那麼問題來了,輸出的 a 會是 1 嗎?我們看程序的執行結果:
大家可以看到,結果並不是 1 , 難道是因爲沒有做 ++ 操作嗎?還是怎樣呢?
方式二: .class
.class 文件前面我又提到過,是 Java 程序的可執行文件,會通過 JVM 中的 ClassLoad 加載到我們的JVM中,換句話說,這個 .class 會決定了我們這個輸出。我們現在去看一下, 答案和我們預想的不一樣的,在.class 中能不能找到答案?
我現在貼出整個 .class 文件:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.java.learn;
public class CompareI {
public CompareI() {
}
public static void main(String[] args) {
int a = 0;
byte var10000 = a;
int var2 = a + 1;
a = var10000;
System.out.println("a = " + a);
}
}
現在我們是不是看到了? 我們一步一步來:
首先我們定義一個變量 a ,
int a = 0;
將 a 的值賦值給一個 叫 var10000 的臨時變量:
byte var10000 = a;
執行 +1 操作:
int var2 = a + 1;
再將我們的 臨時變量的值 還給 a:
a = var10000;
最後輸出 a 的值。
現在該明白爲什麼輸出的結果是 0 , 不是 1 了吧? 1 在變量 var2 裏面,被它截胡了,能咋辦呢?我們現在再來看看之前的那個 .class 文件:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.java.learn;
public class CompareI {
public CompareI() {
}
public static void main(String[] args) {
int i = 0;
byte var10000 = i;
int i = i + 1;
int j = var10000;
System.out.println("i = " + i + ",j = " + j);
int x = 0;
int x = x + 1;
System.out.println("x = " + x + ",y = " + x);
}
}
這裏我們看一下,這裏的 ++x 的操作,是不是就直接是 x=x+1 了?這裏也很好理解是吧。到這裏,我們的這個對於 ++i 和 i++ 的理解就很明確了。
方式三: 字節碼
最後一種方式爲 字節碼 的方式,什麼意思呢?我們從程序的基礎出發,去理解這個 i++ 與 ++i 。我們先來看一下那個讓我們判斷錯誤的程序的字節碼文件長啥樣:
Compiled from "CompareI.java"
public class com.java.learn.CompareI {
public com.java.learn.CompareI();
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: iload_1
3: iinc 1, 1
6: istore_1
7: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
10: new #3 // class java/lang/StringBuilder
13: dup
14: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
17: ldc #5 // String a =
19: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
22: iload_1
23: invokevirtual #7 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
26: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
29: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
32: return
}
這裏怎麼去跟大家解釋呢?一步一步來:
# 將一個常量加載到操作數棧
0: iconst_0
# 將一個數值從操作數棧存儲到局部變量表
1: istore_1
# 將一個本地變量加載到操作數棧
2: iload_1
# 將我們的局部變量1 做 + 1 操作,但是棧中的變量不變
3: iinc 1, 1
到這裏就知道了,棧裏面沒有變。這也就是爲什麼輸出是0 ,不是 1 的原因了。
小結
好了,以上的三個方式是對 i++ 和 ++i 的理解。總的來說,第一種方式是比較簡單快捷的,可以幫助我們在 99% 的場景下去解決問題,但是遇到了 1% 的場景也不要怕,我們直接上 .class ,也可以幫我們解決這剩下的 1% 。
如果說大家對此有什麼不同意見的,特別是後面的字節碼文件的解讀,可以私信或者評論裏面指出,我們大家共同努力,一起進步。