【Java學習筆記】三種方式帶你理解 i++ 與 ++i

引入

我們先看一個例子:

        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 嗎?我們看程序的執行結果:
a++

大家可以看到,結果並不是 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% 。

如果說大家對此有什麼不同意見的,特別是後面的字節碼文件的解讀,可以私信或者評論裏面指出,我們大家共同努力,一起進步。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章