每章一點正能量:每當你想要放棄的時候,就想想是為了什麼才一路堅持到現在。
前言
最近在回顧複習Java基礎中的一些知識點,發現了一些以前見過但是沒有留意卻特別有意思的知識特性,比如這次想分享的Java中一個常見的特性:自動裝箱與拆箱
。這個知識點和特性其實在我們開發過程中經常會遇到。同時我們也會去使用一些基本資料型別或者是封裝資料型別,但是對於他們之間的一些轉換等特性可能不是特別清楚。也可能出現在我們的面試中。本章部分內容從原始碼中解讀一些自動裝箱與拆箱的原理,以及會出現的一些陷阱已經效能等。如有錯誤還請大家及時指出~
問題:
- 基本資料型別與封裝資料型別有哪些區別?
- 什麼是裝箱?什麼是拆箱?
- 裝箱和拆箱都是如何實現的?
- 使用時需要注意哪些問題?
1.基礎知識回顧
-
Java把記憶體劃分成兩種:一種是棧記憶體,另一種是堆記憶體。
-
int是基本型別,直接存數值;而 Integer是類,產生物件時用一個引用指向這個物件。
-
封裝類位於java.lang包中。
-
封裝類是引用傳遞而基本型別是值傳遞
-
基本型別的變數和物件的引用變數都是在函式的棧記憶體中分配 ,而實際的物件是在儲存堆記憶體中
1.1 基本資料型別和封裝型別的區別
我們來看下他們之間有哪些區別:
八種基本資料型別分別是:byte、char、boolean、int、short、float、double、long; 對應的封裝型別分別是:Byte、Character、Boolean、Integer、Short、Float、Double、Long。
基本型別 | 封裝型別 | 位元組長度 | 預設值 |
---|---|---|---|
boolean | Boolean | 1 | false |
byte | Byte | 1 | 0 |
char | Character | 2 | u0000 |
short | Short | 2 | 0 |
int | integer | 4 | 0 |
long | Long | 8 | 0l或0L |
float | Float | 4 | 0.0f或0.0F |
double | Double | 8 | 0.0 |
2. "==" 和 "equal()" 方法
在鞏固了上面的基礎知識點之後,我們再來看下另外的一個知識點 "=="和"equal()"
這兩個判斷符在比較基本資料型別和封裝型別的時候會做的一些事情。
" == ":比較的是基本資料型別,比較的是它們的值
"equals()": 比較的是引用資料型別,根據不同的資料型別呼叫不同的equals方法。在特殊情況下可以重寫equals方法。
a==b並不能判斷a等於b,而是判斷是否為同一個Object。如果我們要判斷他們的值怎麼做呢?用equal或者Objects.equals()(JDK1.7之後新加 的語法)
Objects.equals有什麼好處呢?
如果用a.equals(b) 如果a是null 的話,還會丟擲空指標異常。但是用Objects.equals就沒有問題。因此我們在使用引用型別的時候需要注意,當我們在賦值的時候,兩個變數都是引用同一個Object。
我們以 int與Integer
作為例子,看下"=="和"equal()"方法:
1)基本型和封裝型別進行"=="運算子的比較,封裝型別將會自動拆箱變為基本型後再進行比較,因此Integer(0)會自動拆箱為int型別再進行比較。
2)兩個Integer型別進行"=="比較,如果其值在-128至127,那麼返回true,否則返回false, 這跟Integer.valueOf()的緩衝物件有關,後面會說。
3)兩個封裝型別進行equals()比較,首先equals()會比較型別,如果型別相同,則繼續比較值,如果值也相同,返回true。
4)基本型封裝型別呼叫equals(),但是引數是基本型別,這時候,先會進行自動裝箱,基本型轉換為其封裝型別,再進行3中的比較。
3.什麼是裝箱和拆箱
基本資料(Primitive)型別的自動裝箱(autoboxing)、拆箱(unboxing)是自J2SE 5.0開始提供的功能。Java語言規範中說道:在許多情況下包裝與解包裝是由編譯器自行完成的(在這種情況下包裝稱為裝箱,解包裝稱為拆箱)。 通俗的理解:裝箱:基本型別轉換成封裝型別, 拆箱:封裝型別轉換成基本型別 這麼一個過程。在上面有介紹八種基本型別和對應的封裝型別。下面以Integer與int之間的轉換作為理解:
3.1 自動裝箱(Autoboxing)
Integer a = 2; //Boxing
複製程式碼
簡單的理解:將2裝在一個箱子裡,這個箱子的型別是Integer 。箱子這裡面裝的數值就是2,我們就完成了一次裝箱操作。並把a指向2這個箱子。
Integer b = new Integer(2);//Boxing
複製程式碼
顯示裝箱。生成一個新的箱子 new Integer(); 並且這個箱子的值為2.而且讓b指向這個箱子。
3.2 拆箱(Unboxing)
故名思議就是將物件重新轉化為基本資料型別
int v = a.intValue(); //Unboxing
複製程式碼
簡單的理解:將裡面int的值取出來。拆箱有個很典型的用法就是在進行運算的時候:因為物件時不能直接進行運算的,而是要轉化為基本資料型別後才能進行加減乘除。
例如:
Integer c = 5;
System.out.print(c--);//進行計算時隱含的有自動拆箱
複製程式碼
4. 裝箱拆箱結合原始碼分析
通過第四點我們知道裝箱拆箱的基本概念知識,下面我們同樣以Integer 為例,進入原始碼裡面看看裡面的乾坤。
我們首先看下Integer的大小。
4.1 Integer 大小
可以看出,其定義了Integer的最大值為2^31-1,最小值為-2^31。Integer的基本資料型別為int。
4.2 Integer中的valueOf()方法
再來看看Integer中的valueOf()方法。
可以看出valueOf()方法是個靜態方法。當傳進來的變數值在一個區間之內,直接用IntegerCache.cache[]陣列裡面的數返回,否則new一個新物件。
接著我們來看看IntegerCache類。其實也是會出現坑的一個地方。
4.3 其中存在的陷阱
接著來說下Integer這兒的一個坑,也是比較有意思的地方。
初始化Integer後,IntegerCache會快取[-128,127]之間的資料,這個區間的上限可以配置,取決於java.lang.Integer.IntegerCache.high這個屬性,這個屬性在VM引數裡為-XX:AutoBoxCacheMax=2000進行設定調整或者VM裡設定-Djava.lang.Integer.IntegerCache.high=2000。所以Integer在初始化完成後會快取[-128,max]之間的資料。cache屬於常量,存放在java的方法區中。
同樣,在Long,Byte,Short,我們也可以看到快取,其快取資料長度均是-128到127。這裡不做展開。
另外其他陷阱: 如:
System.out.println(Integer.valueOf(null));
複製程式碼
Integer物件的值可以為null,所以編譯器檢查時不會出現檢查時異常,但是在轉換成int的時候就會丟擲空指標異常。
4. 例題分析
我們通過幾個經典的問題,來看看大家到底理解了裝箱與拆箱
的知識點沒。
- new Integer(5) == 5?
- new Integer(5) == new Integer(5) ?
- Integer.valueOf(5) == Integer.valueOf(5)?
- Integer.valueOf(5).intValue() == 5?
- new Integer(5).equals(new Integer(5))?
4.1 問題一:new Integer(5) == 5?
答案:true。 等號的左邊是一個Object右邊是一個數值,Object和數值怎麼會相等的呢?Java的編譯器很聰明,它會自己去做裝箱和拆箱的操作。這邊它將new Integer(5)做的是Unboxing,它會裡面的value取出來,這時候發現取出來的5等於右邊,所以就為true。
4.2 問題二:new Integer(5) == new Integer(5) ?
答案:false。 new Integer(5) 就是新建一個箱子,這個箱子的值就是5。 == 是判斷這兩個箱子是不是同一個箱子,不是說裡面的值是不是一樣.所以是false。因為他們不是同一個箱子。
4.3 問題三:Integer.valueOf(5) == Integer.valueOf(5)?
答案: true。 Integer.valueOf(5)它會返回一個箱子給我們,箱子裡面的值是5。但是在返回這個箱子給我們的時候,可能會新建一個新的箱子給我們,也可能會使用現有的一個箱子給我們。所以Integer.valueOf(5) == Integer.valueOf(5)。什麼情況下才會相等呢?只有當系統已經將2這個箱子建立好了,並且快取起來的情況下。會把箱子的引用同時發給等號的左邊與右邊。這樣的情況,他們才會互相相等。Integer.valueOf() 是系統給我們分配的一個箱子,我們發現,每次調我們的箱子時候,系統都給了同一個箱子。這個我們的 Integer.valueOf(5) == Integer.valueOf(5)
但是: 可能為false。我們在上面介紹過,在low和high之間,它會返回一個系統已經生產的cache,否則它會生產一個新的出來。看原始碼可以看到low = -128 high = 127。所以當它的值超過了區間後,它就會返回新的箱子,所以就會為false。
我們不用5改用200試一試。
Integer.valueOf(200) == Integer.valueOf(200)
複製程式碼
答案:false。 說明系統對小的數字會使用系統分配的箱子,對於大的數字,系統會重新new一個箱子。面試的時候,可以回答,他們可能相等,也可能不相等。是有系統決定的。
4.4 問題四:Integer.valueOf(5).intValue() == 5?
答案: true。 intValue()做了一個拆箱的操作,將裡面的值5取出來,值5等於5,所以是true。
4.5 問題五:new Integer(5).equals(new Integer(5))?
答案:true。 這裡我們沒有用==而是用equals,equals判斷相等是判斷裡面的值是不是相等,而不是判斷這個箱子是不是同一個,所以我們的答案是true。我們來看看equals的原始碼。判斷裡面的值是不是相等。
列印結果:
文末
本章節主要簡單介紹了自動裝箱與拆箱
的相關知識,希望對大家有所幫助~
今後我會在每張文章開頭增加 每章一點正能量 ,文末增加5個程式設計相關的英語單詞 學點英語。希望大家和我一樣每天都能積極向上,一起學習一同進步!
學點英語
- AWT(Abstract Window Toolkit)抽象視窗工具
- API(Application Programming Interface)應用程式介面
- AOP Aspect Oriented Programming(面向切面程式設計),可以 通過預編譯方式和執行期動態代理實現在不修改原始碼的情況下給程式動態統一 新增功能的一種技術。
- BMP Bean-Managed Persistent(Bean管理的永續性),EJB中由 Bean自己負責永續性管理的方法,Bean的內容的同步(儲存)需要自己編寫程式碼 實現。
- I18N internationalization(國際化),這個單詞的長度是20,然後取 其首尾字母,中間省略的字母剛好18個。
歡迎關注公眾號:Coder程式設計 獲取最新原創技術文章和相關免費學習資料,隨時隨地學習技術知識!
參考文章: