Java第一課
你的解釋不是我想要的
“同學們好,我是教授你們Java101課程的S老師。下面開始我們的第一堂課吧。”
“Java安裝、編輯器安裝、以及執行起hello world程式碼,我已經在課前預習郵件裡,告訴大家要怎麼做了,不知道大家完成的怎麼樣?”
“老師,您的郵件裡就一句話,‘請自行Google’ ...”
“沒錯。”
其實我內心OS是:如果臺下大部分學生,都完成不了預習任務,嗯,那這門課又開不成了,我又可以安心做研究。
不過為了讓這個故事繼續下去,我們姑且假設大部分學生都完成了預習任務吧。
“嗯,同學們很出色,下面再來一起看看這兩段Hello World程式碼”
HelloWorld-1:
public class HelloWorld {
public static void main(String[] args) {
int i = 0;
i = i++;
System.out.println(i);
}
}
HelloWorld-2:
public class HelloWorld {
public static void main(String[] args) {
int i = 0;
i = ++i;
System.out.println(i);
}
}
“相信大家也都知道執行結果了,第一段程式碼是0,第二段程式碼是1。好,我們的第一堂課就是這樣,大家還有什麼疑問嗎?”
大概過了半分鐘,臺下有個同學問道,“老師,我想知道為什麼?為什麼只是換了下順序,結果就不一樣了?”
這是我期待已久的問題,對,就是簡簡單單三個字,“為什麼”
旁邊一同學,說道,“這個我知道。i =i++,會先賦值,再加一,所以結果是0,而i = ++i,會先把i加一,然後再賦值,所以結果是1”
全場感嘆,都向那位同學投以敬佩的目光,畢竟他的理論足以解釋現象。
唯有剛剛提問的同學,說了一句,“你的解釋不是我想要的......”
翻譯官
這堂Java第一課的高潮終於到來了,我很激動。
剛剛這位同學的解釋,不可謂不對,但是終究沒說到點上。
i =i++,會先賦值,再加一,所以結果是0,這個解釋很正確,但是理由在哪?
這只是你的片面之詞呢?還是道聽途說所得?這個解釋不足以服眾。
你寫的程式碼,是高階語言,是給人看的,機器可看不懂。
所以在你寫的程式碼,到機器開始執行中間,肯定有一個翻譯的過程。
Java中,這個翻譯的動作,是由JVM,Java虛擬機器來完成。
大家都知道Java是跨平臺的,所謂“Write Once, Run Anywhere”, 同樣一份程式碼,可以在不同的平臺上執行,不像別的語言,比如C,也許這段程式碼在Linux上正常,去到OS X就有Bug了。
那麼Java是如何實現跨平臺的呢?簡單說,靠的就是JVM這個翻譯官。
你寫好的程式碼,會被編譯成一個.class檔案,也就是Java位元組碼檔案,這裡面記錄的是一系列要在JVM執行的指令。
接著,你拿著這份位元組碼指令,去到任意一個JVM,Linux的JVM也好,OS X的也好,它們都會幫你把它翻譯成對於平臺的機器指令。這就實現了跨平臺、
Java位元組碼是國際通用語言(英語),JVM是翻譯官。
反彙編
回到我們的問題,++i和i++為什麼會不一樣呢?
這就要看這兩行高階語言程式碼,轉成位元組碼指令之後是什麼樣子了。
先來看看HelloWorld-1。首先使用javac把你寫的高階語言,也就是java檔案,編譯成位元組碼檔案。我已經把原始碼中的System.out.println(i)刪掉,這樣我們就可以專心觀察i++和++i:
javac HelloWorld.java
可以看到HelloWorld.java同級目錄下,出現了一個HelloWorld.class檔案。
class檔案裡面都是二進位制的資料。為什麼是二進位制?因為這些都是告訴JVM要做什麼事情的指令,而機器只看得懂0101之類的二進位制。
所以,我們需要對這個二進位制資料,進行反彙編,把它變成人類看得懂的語言,來看看這些二進位制資料都在說些什麼,這裡我們用到javap:
javap -c HelloWorld.class
命令執行後,控制檯列印出一系列的位元組碼指令,其中main函式的位元組碼指令如下:
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: return
這一串的指令,主要涉及到兩個資料結構,一個是運算元棧(operand stack),另一個是區域性變數表(local variable)。前者是棧,後者是陣列。
那麼這些指令都是什麼意思?
不急,下面圖文並茂,給你解釋。
棧和陣列的故事
1、iconst_0
把一個值為0的int值,壓到運算元棧中。
2、istore_1
從運算元棧中彈出一個值,存放到區域性變數表index為1的位置(為什麼不是0,思考題)
pop之前:
pop之後:
以上兩條指令對應的是第一行程式碼 int i = 0:
它實現了給i賦值,並且把i放到區域性變數表的功能。
下面再來看看 i = i++ 對應的指令。
3、iload_1
把區域性變數表中,index=1位置的值,壓到運算元棧中。
4、iinc 1, 1
對區域性變數表index=1位置的值,進行加1操作。
iinc指令包含兩個引數:
- 第一個是index,代表要操作是區域性變數表哪個位置的值;
- 第二個是const,代表要加多少;
現在區域性變數表裡的i其實是等於1的,可是為什麼最後列印出來還是0呢?
問題出在最後一條指令。
5、istore_1
從運算元棧中彈出一個值,將它賦值給區域性變數表中,index為1位置上的值。
pop之前:
pop之後:
完蛋,這下i又變成0了。
至於 i = ++i為什麼最後是1 ,請大家按照上面的思路,自行分析。
其實兩者的差別只在iload_1和iinc 1, 1的順序上。
i = ++i,iinc 1, 1在前,iload_1在後,所以最後結果是1.
上面這些指令的含義,不需要刻意去記,有JVM規範可以檢視:The Java Virtual Machine Instruction Set
這堂課提到的運算元棧和區域性變數表,只是JVM執行時資料區域中,很小的一塊,完整的模型圖是這樣:
運算元棧和區域性變數表,位於圖中的JVM Stack中,也就是我們常說的虛擬機器棧。
End
這堂課的重點,並不在於跟大家解釋i++和++i的區別,而是要給大家引入一個Java中十分重要的觀察角度——JVM.
你寫的程式碼,只是表象,程式不一定按照表象去執行。
萬一發現很奇怪的現象了,莫慌,別忘了中間還有個JVM在作祟。
......
忽然,鬧鐘響了。
“傻蛋,怎麼老是做這個夢。你早就因為開不了課被大學辭退了。”
起床,刷牙洗臉,上班。
今天又會有什麼好玩的需求?
參考
相關文章
- Java 第一課Java
- java第一次正式課程課後習題Java
- Java基礎 第一節 第八課Java
- Java基礎 第三節 第一課Java
- 第一課jdk17,java技術路線JDKJava
- 第一章-JAVA基礎-課後總結和課後習題Java
- hadoop第一課Hadoop
- HarmonyOS 第一課:基礎課程
- HarmonyOS 第一課:中級課程
- Docker-第一課Docker
- 第一節課心得
- java EE開發之Servlet第一課:servlet的建立方式JavaServlet
- 玩幣安全第一課
- 軟體工程第一課軟體工程
- Docker專欄-第一課Docker
- .NET Core 第一節課 - 起源
- 新手接觸spring第一課Spring
- 人工智慧教育第一課人工智慧
- 機器學習進階 第一節 第一課機器學習
- 延世韓國語第一課
- JavaScript 學習初篇(第一課)JavaScript
- OOP課第一階段總結OOP
- 第一課:小李是中國人
- 小姐姐授課第一章
- Sharding-JDBC 快速入門第一課JDBC
- 第一次講課總結
- 10.15 見習後的第一節課
- 你的容器化學習第一課
- JAVA課後作業Java
- java第一週Java
- 機器學習入門第一課:從高中課本談起機器學習
- JAVA入門第三季第一章第九節課後練習題!Java
- HDFS第一次課堂測試
- jQuery第一章課後作業jQuery
- 軟體工程概論18918第一節課軟體工程
- OOP課程第一次部落格OOP
- python第一節課內容及練習Python
- 資料結構 第一節 第六課資料結構