相信大家都知道i++ 和++i,一個先賦值再自增一個是先自增再賦值,在筆試中經常遇到這種問題,那i = i++ + ++i呢,它的原理是什么呢,今天就從底層JVM的執行方式來詳細分析一下,
首先結果是多少?
public static void main(String[] args) {
int i = 0;
i = i++ + ++i;
System.out.println(i);
}//結果輸出 2
另外本人整理了20年面試題大全,包含spring、并發、資料庫、Redis、分布式、dubbo、JVM、微服務等方面總結,下圖是部分截圖,需要的話點這里點這里,暗號CSDN,

為什么是2?
一個.java檔案首先要被編譯成.class檔案jvm才能夠運行,而jvm是根據java代碼生成的位元組碼來確認他要如何運行程式的,說的再通俗一點就是,jvm看不懂java代碼,他能看懂的是位元組碼,而編譯就是這么一個翻譯的程序,
??所以為了了解 i= i++ + ++i的運行原理,我們首先反匯編這段代碼(請先編譯java檔案,Main.java是我的檔案名):在命令列下輸入
javap -c Main.class
可以看到位元組碼是:
0: iconst_0
1: istore_1
2: iload_1
3: iinc 1, 1
6: iinc 1, 1
9: iload_1
10: iadd
11: istore_1
12: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
15: iload_1
16: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
19: return

乍一看大家可能一臉懵,不要怕,這其實很容易,為了不至于引入太多復雜概念,這里只需要知道程式在運行時的兩個區域,一個叫做區域變數表(Local Variable),一個叫做運算元堆疊(Operand Stack),前者的結構類似陣列,用來存盤區域變數,后者的資料結構是堆疊,用來輔助執行指令,
??我一條條解釋上述指令,先看下圖,因為這里只用到了1個區域變數表的位置,所以其他的就沒寫出來,

紅框的內容代表每條指令執行完,這兩個區域的值是多少,建議認真看下每一行的值是怎么來的再往下看,實際上在執行到getstatic #2這條命令的時候,我們的想知道的問題已經計算完了,主要關注這條命令以上的命令即可,并且我們的值最終也存盤在區域變數表1號位置,
??所以最終輸出2是理所當然的,
規律是什么?
既然說class檔案是java檔案的“翻譯”過來的,那么java代碼和位元組碼總有對應關系吧?我們試著找一下,
-
我們說區域變數表是用來存放區域變數的,第二條指令又向區域變數表中存入值了,根據指令的解釋很容易能夠猜到前兩條指令iconst_0 istore_1對應java代碼int i = 0;,
-
我們通過最后的輸出知道了i的值是存在區域變數表1中的,那么istore_1這個向區域變數表1號位置賦值的陳述句一定就是將前面計算得到的i++ + ++i的結果存進表的意思,也就是意味著在執行istore_1陳述句時,運算元堆疊堆疊頂的元素就是我們計算的結果,
-
繼續往上推,iadd指令執行的時候,堆疊頂的兩個元素一定一個就是i++ 另外一個就是++i的值,除去最開始的兩條指令,一共只剩下四條指令了分別是
iload_1
iinc 1, 1
iinc 1, 1
iload_1
猜也能猜出來前兩個對應一條指令,后兩個對應一條,畢竟這么兩個相似的指令不可能翻譯出來位元組碼的命令數還不相等吧,問題是前兩個和后兩個誰對應i++誰對應++i,我們先回憶一下這兩條陳述句在java上有什么不同,簡單說“i++是先用再加,++i是先加再用”,另外需要再講一個東西,我前面說運算元堆疊是用來輔助執行命令的,形象點理解就是運算元堆疊里面的東西是馬上就要拿來用的,而區域變數表是用來暫時先保存下變數的,好了,回到我們剛才的問題,再想一下,你應該就能夠想到:前兩條指令對應i++而后兩條對應++i,前兩條位元組碼的含義是:我準備用1號變數,先放在堆疊里(先用),好了,我已經放在堆疊里了,你在區域變數表里可以加1了(再加),后兩條位元組碼的含義是:你在區域變數表里先加1(先加),然后我要放在堆疊里了(再用),
這樣我們就把每條陳述句及其對應的位元組碼都找出來了,那么規律到底是什么?
我們現在用更加通俗的話來解釋i= i++ + ++i,首先這個陳述句等價于i = (i++) + (++i),執行順序是:
- 計算i++
- 計算++i
- 將前兩個計算的結果加起來賦值給i
看起來好像在說廢話,那么我們結合之前的位元組碼來分析,
- 步驟1還可以分成2步
- 將當前i的值,拷貝一份(假如拷貝出來的元素叫copy1 ),翻譯成代碼:int copy1 = i; (最開始i為0)
- 將i的值加1,翻譯成代碼:i++;(此時i為1)
- 步驟2同樣分成2步
- 將i的值加1,翻譯成代碼:i++;(此時i為2)
- 將當前i的值,拷貝一份(假如拷貝出來的元素叫copy2 ),翻譯成代碼:int copy2 = i;(此時i還是2)
- 將兩個計算結果加起來,i= copy1 + copy2 (也就是0+2)
總結起來:i= i++ + ++i真實執行程序的偽代碼就是
int copy1 = i;
i++
i++
int copy2 = i
i = copy1 + copy2;
那么我們現在來試著算一下 i = ++i + i++ + ++i + ++i (i的初始值是0)
首先我們知道,i = ++i + i++ + ++i + ++i 等價于 i = (++i) + (i++) + (++i) + (++i),
我們將四個括號里的值分別起名為r1、r2、r3、r4,運算式從左向右計算,
- 首先計算r1:++i要先將i加1,然后賦值給r1,所以r1等于1,(執行完這條陳述句時i的值為1)
- 然后計算r2:i++要先將i的值賦值給r2,然后i字加1,所以r2等于1,(執行完這條陳述句時i的值為2)
- 然后計算r3:++i要先將i加1,然后賦值給r3,所以r3等于3,(執行完這條陳述句時i的值為3)
- 然后計算r4:++i要先將i加1,然后賦值給r4,所以r3等于4,(執行完這條陳述句時i的值為4)
- 然后計算r1+r2+r3+r4,等于1+1+3+4,結果為9
- 最后將9賦值給i,(其實此時i是有值的,就是之前的4,但是被剛賦值進來的9給覆寫了,所以就沒能表現出來)
怎么樣,你算出來了嗎?
這篇文章是最近學習jvm以來想通的一個以前一直不明白的問題,在此與大家分享下,如果有不懂的,可以下面留言,如有錯誤也請指出,
最后:
針對最近很多人都在復習等待金三銀四,我這邊也整理了相當多的面試專題資料,也有其他大廠的面經,希望可以幫助到大家,
下面的面試題答案都整理成檔案筆記,也還整理了一些面試資料&最新2020收集的一些大廠的面試真題(都整理成檔案,小部分截圖),有需要的可以點擊進入暗號CSDN

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/231575.html
標籤:java

