1. 程式人生 > >小白學Java:包裝類

小白學Java:包裝類

目錄

  • 小白學Java:包裝類
    • 包裝類的繼承關係
    • 建立包裝類例項
    • 自動裝箱與拆箱
      • 自動裝箱
      • 自動拆箱
    • 包裝型別的比較
      • "=="比較
      • equals比較
    • 自動裝箱與拆箱引發的弊端
      • 自動裝箱弊端
      • 自動拆箱引起的空指標

小白學Java:包裝類

學習了許久的Java,我們知道Java是一種面向物件的語言,萬物皆物件。但是我們之前在說到Java基本資料型別的時候,由於處理物件需要額外的系統開銷,於是出於對效能的考慮,基本資料型別並不做為物件使用。

既然是面向物件的,在Java中許多方法需要把物件作為引數,但是基本型別變數身上沒有任何方法和屬性,於是Java提供了一個簡單的方法,就是為每一個基本資料型別型別都配套提供一個包裝型別,我們便可以在兩者之間來回反覆地橫跳。

包裝類的繼承關係

先看一波包裝型別的繼承圖:

數值型別都直接繼承於父類Number類,非數值型別Character和Boolean直接繼承於Object類。

除此之外,包裝型別的名字也非常好記,除了int->Integerchar->Character兩個比較特殊之外,其他都是基本資料型別的首字母改為大寫即可,如:byte->Byte

通過檢視官方文件,我們可以發現,數值型別繼承的Number類其實是一個抽象類,那麼可想而知,該類中的抽象方法已經在這幾個數值型別中得到實現,看一波:

很明顯,除了最後一個serialVersionUID(這個以後再總結),其他的方法在數值型包裝類中都存在,可以通過這些方法將物件“轉換”為基本型別的數值。

建立包裝類例項

我們再來看看包裝型別的構造器,我們再檢視所有包裝類之後,發現:

  • 所有的包裝型別都不存在無參構造器。
  • 所有包裝類的例項都是不可變的。
  • 一旦建立物件後,它們的內部值就不能進行改變。
    在JDK1.5之前,我們可以這樣把基本資料型別轉換為包裝類物件,這個過程也叫做裝箱,當然反向的過程叫做拆箱:
Integer i1 = new Integer(5);//5
Integer i2 = new Integer("5");//5
  • 第一句呼叫的是傳入int型別引數的構造器,this.value = value,一目瞭然。
  • 第二句呼叫的是傳入String型別引數的構造器,其實又是呼叫了靜態方法parseInt(String s,int radix):
public Integer(String s) throws NumberFormatException {
    this.value = parseInt(s, 10);
}

深究一下,parse(String s,int radix)中的radix其實代表著進位制資訊,而我們的構造器預設讓radix為10,代表著輸出字串s在十進位制下的數,所以除了數字0-9之外,字串中不能有其他的玩意兒,否則會丟擲NumberFormatException的異常。

自動裝箱與拆箱

我們在上面說過,基本資料型別和包裝型別之間的轉換涉及到裝箱與拆箱的操作,為了簡化程式碼,在JDK1.5之後,Java允許基本型別和包裝型別之間可以自動轉換。

自動裝箱

將基本型別直接賦值給對應的引用型別,編譯器在底層自動呼叫對應的valueOf方法。
就像下面這樣:

int i = 5;
Integer in = i; 

我們利用debug除錯工具設上斷點,發現在執行Integer in = i;時,將會自動呼叫下面的方法:

繼續深究其底層實現,我們發現IntegerCache其實是Integer包裝類的一個內部類,我們進入IntegerCache一探究竟:

我們會發現所有的整數型別的(包括Character)包裝類裡都有類似的玩意兒,所以大致執行的規則應該大致相同,在這裡就總結幾點不太一樣的:

  • 只有Integer包裝類才可以更改快取大小。
  • Character容量只有128。
    浮點數型別包裝類並不存在快取機制,是因為在一定的範圍內,該型別的數值並不是有限的。
    看到這,我們大致就可以得出結論,整數數值型別在自動裝箱的時候會進行判斷數值的範圍,如果正好在快取區,那麼就不必建立新的物件,它們將會指向同一地址。Java中另一個例子就是我們說的字串常量池。
    所以下面很火的幾條語句,結果就很明顯了:
int num = 100;
Integer i1 = num;
Integer i2 = num;
System.out.println(i1==i2);//true
//num改為200,結果為false
Integer i1 = 100;
Integer i2 = new Integer(100);
System.out.println(i1 == i2);//false

自動拆箱

將引用型別位元組賦值給對應的基本型別,編譯器在底層自動呼叫對應的xxxvalue方法(如intValue)。

Integer in = 5;
int i = in;

自動拆箱相對來說就稍微簡單一點了,我們還是利用debug工具,發現上面的程式碼將會自動呼叫下面的方法

包裝型別的比較

"=="比較

int num = 100;
Integer i1 = num;
Integer i2 = num;
//都是包裝器型別的引用時,比較是否指向同一物件。
System.out.println(i1==i2);//true

Integer i1 = 128;
int i2 = 128;
//如果包含算數運算子,則底層自動拆箱,即比較數值。
System.out.println(i1 == i2);//true
Integer i3 = 1;
Integer i4 = 129;
System.out.println(i4 == i1+i3);//true

equals比較

equals比較的是同一包裝型別,即比較兩者數值是否相等

Integer i1 = 5;
Integer i2 = 5;
Integer i3 = 10;
//同一包裝型別,比較數值是否相等
System.out.println(i1.equals(i2));//true
System.out.println(i3.equals(i1+i2));//true

Long l1 = 5L;
Long l2 = 10L;
//Long與Integer比較,不是同一型別,false
System.out.println(l1.equals(i1));//false
//先自動拆箱,i1先轉為int,l轉為long,int自動型別提升轉為long,最後相等
System.out.println(l2.equals(l1+i1));//true

自動裝箱與拆箱引發的弊端

自動裝箱弊端

Integer sum = 0;
for(int i = 500;i<5000;i++){
    //先自動拆箱,而後自動裝箱
    sum+=i;
}

在拆箱裝箱操作之後,由於sum數值超過快取範圍,所以會new出4500個毫無用處的例項物件,大大影響了程式的效能。所以在迴圈語句之前,務必宣告正確的變數型別。

自動拆箱引起的空指標

private static Integer sum;
public static void setSum(Integer num,boolean flag){
    sum = (flag)?num:-1;
}

上面的程式碼,當num傳入為null時,即會引發空指標異常,因為包裝類在進行算術運算時(上述是三目運算),如果資料型別不一致,將會先自動拆箱轉換成基本型別進行運算,而null如果呼叫了intValue()方法就會形成空指標。
改進方案:

public static void setSum(Integer num,boolean flag){
//這樣型別一致,便不會自動拆箱了
    sum = (flag)?num:Integer.valueOf(-1);
    
}

參考連結:

Java 自動裝箱與拆箱的實現原理

Java的自動裝箱、拆箱

Integer快取池(IntegerCache)及整型快取池
Java中的自動裝箱與