天天看點

java中的包裝類型

1、包裝類型引入

在實際程式使用中,程式界面上使用者輸入的資料都是以字元串類型進行存儲的。

而程式開發中,我們需要把字元串資料,根據需求轉換成指定的基本資料類型,如年齡需要轉換成int類型,考試成績需要轉換成double類型等。

那麼,想實作字元串與基本資料之間轉換怎麼辦呢?Java中提供了相應的對象來解決該問題,基本資料類型對象包裝類:java将基本資料類型值封裝成了對象。

封裝成對象有什麼好處?可以提供更多的操作基本數值的功能。

我們已經知道,Java的資料類型分兩種:

  • 基本類型:​

    ​byte​

    ​,​

    ​short​

    ​,​

    ​int​

    ​,​

    ​long​

    ​,​

    ​boolean​

    ​,​

    ​float​

    ​,​

    ​double​

    ​,​

    ​char​

  • 引用類型:所有​

    ​class​

    ​和​

    ​interface​

    ​類型

引用類型可以指派為​

​null​

​​,表示空,但基本類型不能指派為​

​null​

​:

String s = null;
int n = null; // compile error!      

那麼,如何把一個基本類型視為對象(引用類型)?

比如,想要把​

​int​

​​基本類型變成一個引用類型,我們可以定義一個​

​Integer​

​​類,它隻包含一個執行個體字段​

​int​

​​,這樣,​

​Integer​

​​類就可以視為​

​int​

​的包裝類

public class Integer {
    private int value;

    public Integer(int value) {
        this.value = value;
    }

    public int intValue() {
        return this.value;
    }
}      

定義好了​

​Integer​

​​類,我們就可以把​

​int​

​​和​

​Integer​

​互相轉換:

Integer n = null;
Integer n2 = new Integer(99);
int n3 = n2.intValue();      

實際上,因為包裝類型非常有用,Java核心庫為每種基本類型都提供了對應的包裝類型:

基本類型 對應的引用類型
boolean java.lang.Boolean
byte java.lang.Byte
short java.lang.Short
int java.lang.Integer
long java.lang.Long
float java.lang.Float
double java.lang.Double
char java.lang.Character

注意int對應的是Integer,char對應的Character,其他6個都是基本類型首字母大寫 

我們可以直接使用,并不需要自己去定義:

2、裝箱操作和拆箱操作

因為​

​int​

​​和​

​Integer​

​可以互相轉換:

int i = 100;
Integer n = Integer.valueOf(i);
int x = n.intValue();      

是以,Java編譯器可以幫助我們自動在​

​int​

​​和​

​Integer​

​之間轉型:

Integer n = 100; // 編譯器自動使用Integer.valueOf(int)
int x = n; // 編譯器自動使用Integer.intValue()      

這種直接把​

​int​

​​變為​

​Integer​

​​的指派寫法,稱為自動裝箱(Auto Boxing),反過來,把​

​Integer​

​​變為​

​int​

​的指派寫法,稱為自動拆箱(Auto Unboxing)。

注意:自動裝箱和自動拆箱隻發生在編譯階段,目的是為了少寫代碼。

裝箱和拆箱會影響代碼的執行效率,因為編譯後的​

​class​

​​代碼是嚴格區分基本類型和引用類型的。并且,自動拆箱執行時可能會報​

​NullPointerException​

3、不變類

所有的包裝類型都是不變類。我們檢視​

​Integer​

​的源碼可知,它的核心代碼如下:

public final class Integer {
    private final int value;
}      

是以,一旦建立了​

​Integer​

​對象,該對象就是不變的。

對兩個​

​Integer​

​​執行個體進行比較要特别注意:絕對不能用​

​==​

​​比較,因為​

​Integer​

​​是引用類型,必須使用​

​equals()​

​比較:

public class Main {
    public static void main(String[] args) {
        Integer x = 127;
        Integer y = 127;
        Integer m = 99999;
        Integer n = 99999;
        System.out.println("x == y: " + (x==y)); // true
        System.out.println("m == n: " + (m==n)); // false
        System.out.println("x.equals(y): " + x.equals(y)); // true
        System.out.println("m.equals(n): " + m.equals(n)); // true
    }
}      

仔細觀察結果的童鞋可以發現,​

​==​

​​比較,較小的兩個相同的​

​Integer​

​​傳回​

​true​

​​,較大的兩個相同的​

​Integer​

​​傳回​

​false​

​​,這是因為​

​Integer​

​​是不變類,編譯器把​

​Integer x = 127;​

​​自動變為​

​Integer x = Integer.valueOf(127);​

​​,為了節省記憶體,​

​Integer.valueOf()​

​​對于較小的數,始終傳回相同的執行個體,是以,​

​==​

​​比較“恰好”為​

​true​

​,但我們絕不能因為Java标準庫的​

​Integer​

​​内部有緩存優化就用​

​==​

​​比較,必須用​

​equals()​

​​方法比較兩個​

​Integer​

​。 

總結:比較值的内容,除了基本類型隻能使用==外,其他類型都需要使用equals

因為​

​Integer.valueOf()​

​​可能始終傳回同一個​

​Integer​

​​執行個體,是以,在我們自己建立​

​Integer​

​的時候,以下兩種方法:

  • 方法1:​

    ​Integer n = new Integer(100);​

  • 方法2:​

    ​Integer n = Integer.valueOf(100);​

方法2更好,因為方法1總是建立新的​

​Integer​

​​執行個體,方法2把内部優化留給​

​Integer​

​的實作者去做,即使在目前版本沒有優化,也有可能在下一個版本進行優化。

我們把能建立“新”對象的靜态方法稱為靜态工廠方法。​

​Integer.valueOf()​

​就是靜态工廠方法,它盡可能地傳回緩存的執行個體以節省記憶體。

 建立新對象時,優先選用靜态工廠方法而不是new操作符。

如果我們考察​

​Byte.valueOf()​

​​方法的源碼,可以看到,标準庫傳回的​

​Byte​

​執行個體全部是緩存執行個體,但調用者并不關心靜态工廠方法以何種方式建立新執行個體還是直接傳回緩存的執行個體。

4、進制轉換

​Integer​

​​類本身還提供了大量方法,例如,最常用的靜态方法​

​parseInt()​

​可以把字元串解析成一個整數:

int x1 = Integer.parseInt("100"); // 100
int x2 = Integer.parseInt("100", 16); // 256,因為按16進制解析      

​Integer​

​還可以把整數格式化為指定進制的字元串:

public class Main {
    public static void main(String[] args) {
        System.out.println(Integer.toString(100)); // "100",表示為10進制
        System.out.println(Integer.toString(100, 36)); // "2s",表示為36進制
        System.out.println(Integer.toHexString(100)); // "64",表示為16進制
        System.out.println(Integer.toOctalString(100)); // "144",表示為8進制
        System.out.println(Integer.toBinaryString(100)); // "1100100",表示為2進制
    }
}      

注意:上述方法的輸出都是​

​String​

​​,在計算機記憶體中,隻用二進制表示,不存在十進制或十六進制的表示方法。​

​int n = 100​

​在記憶體中總是以4位元組的二進制表示:

┌────────┬────────┬────────┬────────┐
│00000000│00000000│00000000│01100100│
└────────┴────────┴────────┴────────┘      

我們經常使用的​

​System.out.println(n);​

​​是依靠核心庫自動把整數格式化為10進制輸出并顯示在螢幕上,使用​

​Integer.toHexString(n)​

​則通過核心庫自動把整數格式化為16進制。

這裡我們注意到程式設計的一個重要原則:資料的存儲和顯示要分離。

Java的包裝類型還定義了一些有用的靜态變量

// boolean隻有兩個值true/false,其包裝類型隻需要引用Boolean提供的靜态字段:
Boolean t = Boolean.TRUE;
Boolean f = Boolean.FALSE;
// int可表示的最大/最小值:
int max = Integer.MAX_VALUE; // 2147483647
int min = Integer.MIN_VALUE; // -2147483648
// long類型占用的bit和byte數量:
int sizeOfLong = Long.SIZE; // 64 (bits)
int bytesOfLong = Long.BYTES; // 8 (bytes)      

最後,所有的整數和浮點數的包裝類型都繼承自​

​Number​

​,是以,可以非常友善地直接通過包裝類型擷取各種基本類型:

// 向上轉型為Number:
Number num = new Integer(999);
// 擷取byte, int, long, float, double:
byte b = num.byteValue();
int n = num.intValue();
long ln = num.longValue();
float f = num.floatValue();
double d = num.doubleValue();      

5、處理無符号整型

在Java中,并沒有無符号整型(Unsigned)的基本資料類型。​

​byte​

​​、​

​short​

​​、​

​int​

​​和​

​long​

​都是帶符号整型,最高位是符号位。而C語言則提供了CPU支援的全部資料類型,包括無符号整型。無符号整型和有符号整型的轉換在Java中就需要借助包裝類型的靜态方法完成。

例如,byte是有符号整型,範圍是​

​-128​

​​~​

​+127​

​​,但如果把​

​byte​

​​看作無符号整型,它的範圍就是​

​0​

​​~​

​255​

​​。我們把一個負的​

​byte​

​​按無符号整型轉換為​

​int​

​:

public class Main {
    public static void main(String[] args) {
        byte x = -1;
        byte y = 127;
        System.out.println(Byte.toUnsignedInt(x)); // 255
        System.out.println(Byte.toUnsignedInt(y)); // 127
    }
}      

因為​

​byte​

​​的​

​-1​

​​的二進制表示是​

​11111111​

​​,以無符号整型轉換後的​

​int​

​​就是​

​255​

​。

類似的,可以把一個​

​short​

​​按unsigned轉換為​

​int​

​​,把一個​

​int​

​​按unsigned轉換為​

​long​

​。

小結

Java核心庫提供的包裝類型可以把基本類型包裝為​

​class​

​;

自動裝箱和自動拆箱都是在編譯期完成的(JDK>=1.5);

裝箱和拆箱會影響執行效率,且拆箱時可能發生​

​NullPointerException​

​;