裝箱和拆箱是Java中提供的兩個有用的語法糖。java
裝箱是指將基本數據類型自動轉換爲它的包裝器類型。如int到Integer的轉換。web
拆箱是指將包裝器類型轉換爲對應的基本數據類型。如Integer到int的轉換。shell
如下是一個例子:微信
Integer num1 = 1000;
int num2 = num1;
其中num1是一個Integer類型的對象,這裏對它的賦值操做就是一個裝箱的過程。app
num2是一個基本類型int的變量,用num1給它賦值就是一個拆箱的過程。spa
下面咱們從字節碼的角度看一下裝箱和拆箱是如何實現的吧。code
那麼如何獲取到Java代碼的字節碼呢?其實咱們在使用javac對Java源代碼進行編譯後獲得的class文件就是其字節碼文件。orm
可是這個class文件是二進制形式存在的,是沒法直接閱讀的。因此還須要另一個命令javap幫咱們把class文件解析爲可讀的形式。對象
咱們將如下代碼保存在test.java文件中(這裏省略了類目、main方法等):blog
Integer num1 = 1000;
int num2 = num1;
javap 命令的使用
而後用javac進行編譯,再用javap -v進行反編譯。javap加上-v參數能夠看到較爲全面的信息。
javac test.java
javap -v test
使用javap反編譯後,咱們能夠獲得以下結果:
0: sipush 1000
3: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
6: astore_1
7: aload_1
8: invokevirtual #3// Method java/lang/Integer.intValue:()I
11: istore_2
這裏的反編譯結果包含6條字節碼指令,弄懂了這些指令,也就清楚了一個普通的拆箱和裝箱的實現原理。
因爲這些指令對應着在棧幀上的各類操做,咱們首先回顧一下棧幀的概念。
相信你們對Java運行時數據區域中的Java虛擬機棧不會陌生。
每個Java方法從調用直至執行完成的過程,就對應着一個棧幀在虛擬機棧中入棧和出找的過程。
而在方法的執行過程就是在棧幀上進行各類操做。
這裏咱們主要關注棧幀中的操做數棧和局部變量表。
下面咱們詳細分析下上述6條指令的執行過程。
Java代碼
Integer num1 = 1000;
int num2 = num1;
對應字節碼
0: sipush 1000
3: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
6: astore_1
7: aload_1
8: invokevirtual #3// Method java/lang/Integer.intValue:()I
11: istore_2
第一句:sipush 1000
sipush 是一個入棧操做,表示把short類型的數字放入棧幀中。執行完這句後棧幀的狀況以下:
第二句:invokestatic #2
這一句是調用一個static方法。
那麼具體調用的是哪一個方法呢?
能夠看到後面還有一個#2參數。這個#2能夠理解爲常量池的一個索引。
咱們使用javap -v命令除了輸出了上述代碼對應的字節碼外。還有一個重要的部分是常量池。
本次反編譯的常量池以下圖所示:
關於常量池這裏再也不多說,你們暫時知道這回事就行。
回到咱們的主題。咱們如今知道了invokestatic #2這句指令調用的是Integer.valueOf方法,那麼入參是誰呢?
還記得上一條指令幹了什麼事嗎?
對。就是把1000這個數字放到操做數棧的棧頂。棧頂的這個數字1000其實就是Integer.valueOf的入參。
Integer.valueOf的返回值是一個Integer對象。執行完這條指令後,1000出棧,獲得的結果Integer對象入棧。這個Integer對象就是咱們Java代碼裏的num1。
這時,棧幀的狀況以下:
這句表示把操做數棧的棧頂元素彈出,放到局部變量表下標爲1的位置。
執行以後的結果以下:
到這一句執行完,Java代碼中的Integer num1 = 1000,就算執行完了。
前三句字節碼,完成了裝箱的操做。經過上述分析,咱們知道了整型的裝箱就是調用Integer.valueOf方法實現的。
對於其它的數據類型都是相似的。
後三句字節碼對應int num2 = num1的過程。
第四句:aload_1
表示將局部變量表位置爲1的元素,壓入操做數棧。
這一句執行後結果以下。
和第二句化invokestatic相似,這一句也是對方法的調用執行。
只不過這裏調用的是一個示例方法。
參加第二句分析時給出的常量池狀況,這裏調用的方法是Integer的intValue方法。
操做數是上一步壓棧的Integer對象。
執行後結果以下:
第六句:istore_2
將操做數棧的元素存入局部變量表第二個位置。
最後局部變量表的1和2的位置就分別存着num1和num2。
經過後三句字節碼的分析咱們知道了,Integer類型的拆箱是使用Integer.intValue實現的。
對於其它的包裝器類型也是相似的。
本文介紹了裝箱和拆箱的含義。而後經過javap命令拿到了裝拆箱的字節碼實現。並一句句地分析了這些字節碼。
最後得出了對於整數類型,裝箱是使用Integer.valueOf方法、拆箱是使用Integer.intValue方法來實現的結論。
若是本文對您有幫助,歡迎關注個人原創微信公衆號「Java技術小站」第一時間接收個人更多文章