JavaSE基礎:包裝類

胖先森發表於2018-01-31

包裝類

1.為什麼需要包裝類?

Java在設計之初有一個基本原則:一切皆物件,一切的操作都要求用物件的形式進行描述。但是這裡面就會出現一個矛盾,基本資料型別不是物件。那麼我們會如何修復這個BUG呢?最簡單的做法是將基本資料型別作為一個類的屬性儲存起來,這樣就相當於把基本資料型別包裝了一下.

實現基本資料型別的包裝類

package com.shxt.demo01;

public class Int {//類
    private int number;//基本資料型別
    
    public Int(int number){//提供一個引數的建構函式,傳入基本資料型別
        this.number = number;
    }
    
    public int intValue(){//取得包裝類中的資料
        return this.number;
    }
    
    //還可以提供其他的方法
}
複製程式碼
package com.shxt.demo01;

public class MyTest {
    public static void main(String[] args) {
        Int temp = new Int(100);    //將基本資料型別包裝後變為類
        int result = temp.intValue();       //從類中取得基本資料型別
        System.out.println(result+result);
    }
}
複製程式碼

程式碼分析:

我們實現了基本資料型別轉成Java物件的方式,Java中給我們提供了類似的實現類

包裝類表格

基本資料型別 包裝類
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean

我們觀察上述的表格發現除了int->Integer,char->Character,其他的都是基本資料型別的首字母大寫,很好記憶的.

但是以上給出的包裝類又分為兩種子型別:

  • 物件型包裝類(Object直接子類):Character、Boolean。
  • 數值型包裝類(Number直接子類):Byte、Short、Integer、Float、Double、Long。

Number是一個抽象類,裡面一共定義了六個操作方法:intValue()、shortValue()、byteValue()、floatValue()、longValue()、doubleValue()。

2.裝箱和拆箱

現在已經存在有基本資料型別與包裝類,那麼這兩種變數間的轉換就通過以下方式定義。

  • 裝箱操作:將基本資料型別變為包裝類的形式。
    • 每個包裝類的構造方法都可以接收各自資料型別的變數。
  • 拆箱操作:從包裝類之中取出被包裝的資料。
    • 利用Number類中提供的一系列的:xxxValue()方法完成。

示例1-以int和Integer為例演示裝箱和拆箱操作的過程

public class Demo01 {
    public static void main(String args[]) {
        Integer obj = new Integer(10);  // 將基本資料型別裝箱
        int temp = obj.intValue();  	// 將基本資料型別拆箱
        System.out.println(temp * 2);  // 結果為20
    }
}
複製程式碼

之前使用所編寫的Int類,現在換成了Integer這個系統類。

示例2-以double和Double為例演示裝箱和拆箱操作的過程

public class Demo02 {
    public static void main(String args[]) {
        Double obj = new Double(10.2);  	// 將基本資料型別裝箱
        double temp = obj.double Value();  	// 將基本資料型別拆箱
        System.out.println(temp * 2);  		// 結果為20.4
    }
}
複製程式碼

示例3-以boolean和Boolean為例演示裝箱和拆箱操作的過程(不是Number子類)

public class Demo03 {
    public static void main(String args[]) {
        Boolean obj = new Boolean(false);  	// 將基本資料型別裝箱
        boolean temp = obj.booleanValue();  // 將基本資料型別拆箱
        System.out.println(temp);  			// 結果為false
    }
}
複製程式碼

現在可以發現,所有的包裝類都使用了同樣形式的方法進行操作。 在JDK1.5之前能夠使用的操作都是以上形式的程式碼,但是JDK1.5之後,Java為了方便開發提供了自動裝箱自動拆箱的機制,並且可以直接利用包裝類的物件進行數學計算。

示例4-以int和Integer為例觀察自動裝箱和自動拆箱操作的過程

public class Demo04 {
    public static void main(String args[]) {
        Integer obj = 10;  	// 自動裝箱 int->Integer
        int temp = obj;  	// 自動拆箱 Integer->int 實際上呼叫了obj.intValue()方法
        obj ++;
        System.out.println(temp * obj);  // 結果為110
    }
}
複製程式碼

示例5-以boolean和Boolean為例觀察自動裝箱和自動拆箱操作的過程(不是Number子類)

public class Demo05 {
    public static void main(String args[]) {
        Boolean obj = false;  	// 自動裝箱 boolean->Boolean
        boolean flag = obj;  	// 自動拆箱 Boolean->boolean
      	if(!flag){
        	System.out.println("Boolean不是Number的子類");
      	}
    }
}
複製程式碼

重點:正式因為Java提供了自動裝箱和自動拆箱的機制,那麼Object可以接收一切的資料型別(Object可以統一天下了)

轉換流程:基本資料型別 → 自動裝箱(成為物件) → 向上轉型為Object。

示例6-以Object接收int資料型別演示轉換過程

public class Demo06 {
    public static void main(String args[]) {
        Object obj = 10;  	// int->Integer(自動裝箱為物件)->Object
        int temp = (Integer)obj;  	// Object->Integer(強制型別轉換為包裝類)->自動拆箱
        obj ++;
        System.out.println(temp * obj);  // 結果為110
    }
}
複製程式碼

“莫名其妙”的NullPointException

在我們開發過程中,碰到過不少因為請求引數或者介面定義欄位設定為int(或者其他基本型別)而導致NullPointException(空指標異常)。程式碼大致地執行步驟如下所示,當然不會跟這個完全一樣。

public class Demo06 {
    public static void main(String args[]) {
       Integer a = null;

		int b = a; // 丟擲NullPointException a.intValue();
    }
}
複製程式碼

程式碼分析:

上面的程式碼可以編譯通過,但是會丟擲空指標異常(NullPointException)。

前面已經說過了,int b = a實際上是int b = a.intValue()

由於a的引用值為null,在空物件上呼叫方法就會丟擲NullPointException。

3.==和equlas()

大家都應該清楚明瞭的瞭解兩者的區別,

一句話說就是 == 比較的是記憶體中地址,equlas() 對比的為數值,因為基本型別相同的數值指向的同一塊記憶體,所以可以用==來比較,而引用型別則不可以。

public class Demo07 {
    public static void main(String args[]) {
        Integer obja = 10;  // 直接賦值
        Integer objb = 10;	// 直接賦值
        Integer objc = new Integer(10);  	// 構造方法
        System.out.println(obja == objb); 	// true 不是比較記憶體地址嗎?為什麼?
        System.out.println(obja == objc);	// false
        System.out.println(objb == objc);	// false
        System.out.println(obja.equals(objc));	// true
    }
}
複製程式碼

程式碼分析:

obja == objb不是應該比較記憶體地址嗎?為什麼能相等呢?我們需要解決這個問題,原始碼分析

在使用包裝類的時候很少會利用構造方法完成,幾乎都是直接賦值(這一點與String相同),但是在內容是否相等的時候,請一定要記住使用equals()方法。

兩個包裝類引用相等性

在Java中,“==”符號判斷的記憶體地址所對應的值得相等性,具體來說,基本型別判斷值是否相等,引用型別判斷其指向的地址是否相等。看看下面的程式碼,兩種類似的程式碼邏輯,但是得到截然不用的結果。

public class Demo08 {
    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
    }
}
複製程式碼

這個必須從原始碼中才能找到答案。Integer類中的valueOf()方法的原始碼如下:

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high) // 判斷實參是否在可快取範圍內,預設為[-128, 127]
        return IntegerCache.cache[i + (-IntegerCache.low)]; // 如果在,則取出初始化的Integer物件
   	return new Integer(i); // 如果不在,則建立一個新的Integer物件
}
複製程式碼

程式碼分析:

由於127屬於[-128, 127]集合範圍內,所以valueOf()每次都會取出同一個Integer物件,故第一個“==”判斷結果為true;而128不屬於[-128, 127]集合範圍內,所以valueOf()每次都會建立一個新的Integer物件,由於兩個新建立的物件的地址不一樣,故第一個“==”判斷結果為false。

再次分析比較過程

package com.shxt.demo01;

public class MyTest {
    public static void main(String[] args) {
        Integer a = 1;
        Integer b = 2;
        Integer c = 3;
        Integer d = 3;
        Integer e = 321;
        Integer f = 321;
        Long g = 3L;
        Long h = 2L;

        System.out.println(c==d); // 快取機制,返回值true
        System.out.println(e==f); // 超出了快取機制, 返回值false
      	// (a+b) 計算了自動拆箱變成了int型別, c比較需要自動拆箱變成int , 返回值true
        System.out.println(c==(a+b));
        //(a+b) 計算了自動拆箱變成了int型別,因為使用equals比較內容(a+b)的結果有自動裝箱,返回值true
      	System.out.println(c.equals(a+b));
      	// (a+b) 計算了自動拆箱變成了int型別, g自動拆箱變成long型別, (a+b)的結果自動型別轉換為long , 返回值true
        System.out.println(g==(a+b));
      	//(a+b) 計算了自動拆箱變成了int型別,因為使用equals比較內容(a+b)的結果有自動裝箱Integer
      	//Long和Integer比較 , 返回值false
        System.out.println(g.equals(a+b));
      	//涉及到自動型別轉換, 返回值true
        System.out.println(g.equals(a+h));
    }
}
複製程式碼

4.資料型別轉換(核心)

使用包裝類最多的情況實際上是它的資料型別轉換功能上,在包裝類裡面,最大的優點就是提供將String型資料變為基本資料型別的方法,使用幾個代表的類做說明:

  • Integer類:public static int parseInt(String s)。
  • Double 類:public static double parseDouble(String s)。
  • Boolean類:public static boolean parseBoolean(String s)。

**特別注意:**Character類裡面並不存在字串變為字元的方法,因為String類有一個charAt()的方法可以根據索引取出字元內容,並且一個字元的長度才有一位。

範例1:將字串變為int型資料

public class Demo01 {
    public static void main(String args[]) {
        String str = "123";  // 字串
        int temp = Integer.parseInt(str);//String->int
        System.out.println(temp * 2);
    }
}
複製程式碼

此時實現了字串變為基本資料型別的操作。但是在這樣的轉換過程之中請一定要注意:被轉換為數字的字串一定要由數字所組成。如果不是數字組成,轉換過程中會報異常:NumberFormatException

範例2:錯誤程式碼

public class Demo02 {
    public static void main(String args[]) {
        String str = "1sss3";  // 字串
        int temp = Integer.parseInt(str);
        System.out.println(temp * 2);
    }
}
複製程式碼
Exception in thread "main" java.lang.NumberFormatException:For input string:"1sss3"
複製程式碼

範例3:將字串變為double型資料

public class Demo03 {
    public static void main(String args[]) {
        String str = "13";  // 字串
        double temp = Double.parseDouble(str);
        System.out.println(temp * 2); //輸出結果 26.0
    }
}
複製程式碼

範例4:將字串變為boolean型資料

public class Demo04 {
    public static void main(String args[]) {
        String str = "true";  // 字串
        boolean flag = Boolean.parseBoolean(str);
        if(flag) {
            System.out.println("滿足條件!"); 
        } else {
            System.out.println("不滿足條件!");
        }
    }
}
//控制檯輸出: 條件滿足
複製程式碼

範例5:將字串變為boolean型資料

public class Demo05 {
    public static void main(String args[]) {
        String str = "hanpang";  // 錯誤字串
        boolean flag = Boolean.parseBoolean(str);
        if(flag) {
            System.out.println("滿足條件!");
        } else {
            System.out.println("不滿足條件!");
        }
    }
}
//控制檯輸出: 不滿足條件!
複製程式碼

程式碼分析:

在Boolean進行轉換的過程裡面,如果要轉換的字串不是true或者是false,那麼將統一按照false進行處理。

現在既然實現了字串變為基本資料型別操作,那麼也一定可以實現基本資料型別變為字串的操作,對於此類操作有兩種做法:

  • 操作一:任何基本資料型別與字串使用了“+”操作之後都表示變為字串。

    public class Demo06 {
        public static void main(String args[]) {
            int num = 100;
            String str = num + "";  // 變為String
            System.out.println(str.replaceAll("0", "9"));
        }
    }
    
    //控制檯輸出: 199
    複製程式碼

    這樣的操作雖然可以簡單的完成,但是會存在有垃圾的問題。

  • 操作二:public static String valueOf(資料型別 變數) 開發推薦

    public class Demo07 {
        public static void main(String args[]) {
            int num = 100;
            String str = String.valueOf(num);  // 變為String
            System.out.println(str.replaceAll("0", "9"));
        }
    }
    複製程式碼

    這樣的轉換不會產生垃圾,所以在開發時往往會使用以上做法。

5.小結

  • 一定要清楚JDK1.5之後才提供有自動裝箱與拆箱操作。
  • 字串與基本資料型別的互相轉換:
    • 字串變為基本資料型別,依靠包裝類的parseXxx()方法。
    • 基本資料型別變為字串,依靠String.valueOf(資料型別 變數)方法。

相關文章