最近文章更新頻率慢了,因為最近在準備暑期實習,之前尋思著一邊復習一邊寫文章,兩全其美。后來發現一篇讀起來比較舒服的文章寫出來加上配圖得花上四五個小時甚至更多,但這個知識點我可能半個小時就能復習完了,春招在即,時間比較緊迫,所以最近文章可能改為一周一更或者一周兩更,希望各位理解。另外,有和我一樣在準備暑期實習的小伙伴可以聯系我互相交流
全文脈絡思維導圖如下:
1. 為什么需要包裝類
在 Java 中,萬物皆對象,所有的操作都要求用對象的形式進行描述。但是 Java 中除了對象(引用類型)還有八大基本類型,它們不是對象。那么,為了把基本類型轉換成對象,最簡單的做法就是將基本類型作為一個類的屬性保存起來,也就是把基本數據類型包裝一下,這也就是包裝類的由來。
這樣,我們先自己實現一個簡單的包裝類,以包裝基本類型?int?為例://?包裝類?MyInt
public?class?MyInt?{
private?int?number;?//?基本數據類型
public?Int?(int?number){?//?構造函數,傳入基本數據類型
this.number?=?number;
}
public?int?intValue(){?//?取得包裝類中的數據
return?this.number;
}
}
測試一下這個包裝類:public?static?void?main(String[]?args)?{
MyInt?temp?=?new?Int(100);?//?100?是基本數據類型,?將基本數據類型包裝后成為對象
int?result?=?temp.intValue();?//?從對象中取得基本數據類型
System.out.println(result);
}
當然,我們自己實現的這個包裝類非常簡單,Java 給我們提供了更完善的內置包裝類:基本類型對應的包裝類(位于 java.lang 包中)byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean
前 6 個類派生于公共的超類?Number,而?Character?和?Boolean?是?Object?的直接子類。
來看看包裝類的聲明,以?Integer?為例:
被?final?修飾,也就是說 Java 內置的包裝類是無法被繼承的。
2. 裝箱與拆箱
OK,現在我們已經知道了,存在基本數據類型與其對應的包裝類,那么,他們之間互相的轉換操作就稱為裝箱與拆箱:裝箱:將基本數據類型轉換成包裝類(每個包裝類的構造方法都可以接收各自數據類型的變量)
拆箱:從包裝類之中取出被包裝的基本類型數據(使用包裝類的 xxxValue 方法)
下面以?Integer?為例,我們來看看 Java 內置的包裝類是如何進行拆裝箱的:Integer?obj?=?new?Integer(10);??//?自動裝箱
int?temp?=?obj.intValue();??//?自動拆箱
可以看出,和上面我們自己寫的包裝類使用方式基本一樣,事實上,Integer?中的這兩個方法其底層實現和我們上述寫的代碼也是差不多的。
不知道各位發現沒,value?被聲明為?final?了,也就是說一旦構造了包裝器,就不允許更改包裝在其中的值。
另外,需要注意的是,這種形式的代碼是?JDK 1.5 以前的!!!JDK 1.5 之后,Java 設計者為了方便開發提供了自動裝箱與自動拆箱的機制,并且可以直接利用包裝類的對象進行數學計算。
還是以?Integer?為例我們來看看自動拆裝箱的過程:Integer?obj?=?10;??//?自動裝箱.?基本數據類型?int?->?包裝類?Integer
int?temp?=?obj;??//?自動拆箱.?Integer?->?int
obj?++;?//?直接利用包裝類的對象進行數學計算
System.out.println(temp?*?obj);
看見沒有,基本數據類型到包裝類的轉換,不需要像上面一樣使用構造函數,直接?=?就完事兒;同樣的,包裝類到基本數據類型的轉換,也不需要我們手動調用包裝類的 xxxValue 方法了,直接?=?就能完成拆箱。這也是將它們稱之為自動的原因。
我們來看看這段代碼反編譯后的文件,底層到底是什么原理:Integer?obj?=?Integer.valueOf(10);
int?temp?=?obj.intValue();
可以看見,自動裝箱的底層原理是調用了包裝類的?valueOf?方法,而自動拆箱的底層調用了包裝類的?intValue()?方法。
3. 不簡單的 Integer.valueOf
我們上面已經看過了用于自動拆箱的?intValue?方法的源碼,非常簡單。接下來咱來看看用于自動裝箱的?valueOf,其他包裝類倒沒什么好說的,不過?Integer?中的這個方法還是有點東西的:
IntegerCache?又是啥,點進去看看:
IntegerCache?是?Integer?類中的靜態內部類,綜合這兩段代碼,我們大概也能知道,IntegerCache?其實就是個緩存,其中定義了一個緩沖區?cache,用于存儲?Integer?類型的數據,緩存區間是 [-128, 127]。
回到?valueOf?的源碼:它首先會判斷 int 類型的實參 i 是否在可緩存區間內,如果在,就直接從緩存?IntegerCache?中獲取對應的?Integer?對象;如果不在緩存區間內,則會 new 一個新的?Integer?對象。
結合這個特性,我們來看一個題目,兩種類似的代碼邏輯,但是卻得到完全相反的結果。:public?static?void?main(String?args[])?{
Integer?a1?=?127;
Integer?a2?=?127;
System.out.println(a1?==?a2);?//?true
Integer?b1?=?128;
Integer?b2?=?128;
System.out.println(b1?==?b2);?//?false
}
我們知道,==?擁有兩種應用場景:對于引用類型來說,判斷的是內存地址是否相等
對于基本類型來說,判斷的是值是否相等
從 a1 開始看,由于其值在?InterCache?的緩存區間內,所以這個?Integer?對象會被存入緩存。而在創建 a2 的時候,由于其值和 a1 相等,所以直接從緩存中取出值為 127 的?Integer?對象給 a2 使用,也就是說,a1 和 a2 這兩個?Integer?的對象引用都指向同一個地址。
對于 b1 和 b2 來說,由于 128 不在?IntegerCache?的緩存區間內,那就只能自己老老實實開辟空間了,所以 b1 和 b2 指向不同的內存地址。
很顯然,由于?InterCache?緩存機制的存在,可能會讓我們在編程的時候出現困惑,因此最好使用?.equals?方法來比較?Integer?值是否相等。Integer?重寫了?.equals?方法:
當然,其他包裝類雖然沒有緩存機制,但是也都重載了?.equals?方法,用于根據值來判斷是否相等。因此,得出結論,使用?equals?方法來比較兩個包裝類對象的值。
4. Object 類可以接收所有數據類型
綜上,有了自動拆裝箱機制,基本數據類型可以自動的被轉為包裝類,而?Object?是所有類的父類,也就是說,Object?可以接收所有的數據類型了(引用類型、基本類型)!!!
不信你可以試試,直接用?Object?類接收一個基本數據類型?int,完全是可以的。Object?obj?=?10;
int?temp?=?(Integer)?obj;
解釋一下上面這段代碼發生了什么,下面這張圖很重要,大家仔細看:
5. 包裝類在集合中的廣泛使用
其實包裝類最常見的使用就是在集合中,因為集合不允許存儲基本類型的數據,只能存儲引用類型的數據。那如果我們想要存儲 1、2、3 這樣的基本類型數據怎么辦?舉個例子,我們可以如下聲明一個?Integer對象的數組列表:ArrayList?list?=?new?ArrayList<>();
往這個列表中添加?int?型數據:list.add(3);
上面這個調用在底層將會發生自動裝箱操作:list.add?(Integer.valueOf(3));
基本數據類型?int?會被轉換成?Integer?對象存入集合中。
我們再來從這個集合中根據某個下標 i 獲取對應的?Integer?對象,并用基本數據類型?int?接收:int?n?=?list.get(i);
上面這個調用在底層將會發生自動拆箱操作:int?n?=?list.get(i).intValue();
6. 數據類型轉換
另外,除了在集合中的廣泛應用,包裝類還包含一個重要功能,那就是提供將String型數據變為基本數據類型的方法,使用幾個代表的類做說明:
Integer:
Double:
Boolean:
這些方法均被?static?標識,也就是說它們被各自對應的所有對象共同維護,直接通過類名訪問該方法。舉個例子:String?str?=?"10";
int?temp?=?Integer.parseInt(str);//?String?->?int
System.out.println(temp?*?2);?//?20
需要特別注意的是:Character?類里面并不存在字符串變為字符的方法,因為?String?類中已經有一個?charAt()的方法可以根據索引取出字符內容。