首頁 > 軟體

Java包裝類的概述與應用

2022-04-06 19:00:13

一、包裝類概述

Java有8種基本資料型別:整型(byte、short、int、long)、浮點型(float、double)、布林型boolean、字元型char,相對應地,Java提供了8種包裝類Byte、Short、Integer、Long、Float、Double、Boolean、Character。包裝類建立物件的方式就跟其他類一樣。

Integer num = new Integer(0);    //建立一個數值為0的Integer物件

二、包裝類的自動裝箱、自動拆箱機制 

上面的構造物件語句實際上是基本資料型別向包裝類的轉換。在應用中我們經常需要進行基本型別資料和包裝類物件之間的互轉。

Integer num1 = new Integer(1);	//基本資料型別轉為包裝類
int num2 = num1.intValue();		//包裝型別轉為基本資料型別
System.out.println(num1 +"	"+ num2);

而Java為了方便我們使用,以及出於其他目的如效能調優,給我們提供了自動裝箱、拆箱機制。這種機制簡化了基本型別和包裝型別的轉換。

//1、包裝類中的自動裝箱拆箱機制
Integer  num1 = 1;		//自動裝箱
int num2 = num1;		//自動拆箱
System.out.println(num1 +"	"+ num2);

當使用jad工具對上面的程式碼進行反編譯時,結果如下。

Integer integer = Integer.valueOf(1);
int i = integer.intValue();
System.out.println((new StringBuilder()).append(integer).append("t").append(i).toString());

可見,Java編譯器幫我們完成了轉換操作。另外,我們可以看到,除了使用new關鍵字,還可以使用Integer類的valueOf()方法建立一個Integer物件。這兩個方式是有所區別的,我們下面會說到。

三、包裝類中的快取機制

前面說到建立包裝類物件有兩種方式:new關鍵字、valueOf()方法。我們來看一段程式碼感受一下它們的區別。

//2、包裝類中的快取機制
Integer num3 = 10;
Integer num4 = 10;
Integer num5 = new Integer(20);
Integer num6 = new Integer(20);
Integer num7 = 128;
Integer num8 = 128;
System.out.println((num3==num4) +"	"+ num3.equals(num4));
System.out.println((num5==num6) +"	"+ num5.equals(num6));
System.out.println((num7==num8) +"	"+ num7.equals(num8));

執行結果為

我們看下它的反編譯程式碼

Integer integer = Integer.valueOf(10);
Integer integer1 = Integer.valueOf(10);
Integer integer2 = new Integer(20);
Integer integer3 = new Integer(20);
Integer integer4 = Integer.valueOf(128);
Integer integer5 = Integer.valueOf(128);
System.out.println((new StringBuilder()).append(integer == integer1).append("t").append(integer.equals(integer1)).toString());
System.out.println((new StringBuilder()).append(integer2 == integer3).append("t").append(integer2.equals(integer3)).toString());
System.out.println((new StringBuilder()).append(integer4 == integer5).append("t").append(integer4.equals(integer5)).toString());

首先,我們檢視Integer的valueOf()方法的原始碼

    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

再檢視下Integer的內部類IntegerCache的cache陣列成員、low、high成員

        static final int low = -128;
        static final int high;
        static final Integer cache[];
 
        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;
 
            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);
 
            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }

可以發現,只要Integer類第一次被使用到,Integer的靜態內部類就被載入,載入的時候會建立-128到127的Integer物件,同時建立一個陣列cache來快取這些物件。當使用valueOf()方法建立物件時,就直接返回已經快取的物件,也就是說不會再新建物件;當使用new關鍵字or使用valueOf()方法建立小於-128大於127的值物件時,就會建立新物件。

//2、包裝類中的快取機制
Integer num3 = 10;
Integer num4 = 10;
Integer num5 = new Integer(20);
Integer num6 = new Integer(20);
Integer num7 = 128;
Integer num8 = 128;

由於num3、num4都小於等於127,它們指向的是同一個快取的Integer物件,所以用==進行比較的結果是true;num5、num6由於使用new關鍵字指向的是兩個不同的新物件,結果為false;num7、num8雖然是採用自動裝箱的方式,但執行valueOf()方法的時候,由於不滿足條件i >= IntegerCache.low && i <= IntegerCache.high,而同樣新建了兩個不同的新物件,結果同樣是false。

接著,我們再來看看原始碼中Integer的equals()方法的實現

    public boolean equals(Object obj) {
        if (obj instanceof Integer) {
            return value == ((Integer)obj).intValue();
        }
        return false;
    }

可見equals()方法比較的是Integer物件的值,而不是像==一樣比較的是物件是否是同一個物件。所以,當需要比較兩個Integer物件的值是否相等時,記住要用equals()方法。用==比較的話由於快取機制的存在,可能產生一些讓人困擾的結果。

此外,在8種包裝型別中,有快取區的有Character、Byte、Short、Integer、Long,而且它們的實現方式基本一樣,都是-128到127的快取範圍。Boolean雖然沒有快取區,但是因為只有兩個值true、false,所以Boolean在成員變數中就建立了兩個相應的物件。沒有快取區的只有Float、Double,之所以沒有原因很簡單,即便是0到1這麼小的範圍,浮點數也有無數個,使用快取區快取它們不具備可能性和實用性。

快取區的存在使得常用的包裝類物件可以得到複用,這有利於提升效能。當我們需要建立新物件的時候再new一個,增加了靈活性。

四、包裝類的四則運算、位運算、比較運算、邏輯運算

1、四則運算和位運算

//四則運算、位運算
Integer num9 = 1;
Integer num10 = 2;
Integer num11 = num9 + num10; 
Short num12 = 5;
Integer num13 = num9 + num12;
Long num14 = num9 + 10L;
System.out.println(num9 << 1);	//位運算
System.out.println(num9 +"	"+ num10 +"	"+ num11 +"	"+ num12 +"	"+ num13 +"	"+ num14);

反編譯結果如下

        Integer integer = Integer.valueOf(1);
        Integer integer1 = Integer.valueOf(2);
        Integer integer2 = Integer.valueOf(integer.intValue() + integer1.intValue());
        Short short1 = Short.valueOf((short)5);
        Integer integer3 = Integer.valueOf(integer.intValue() + short1.shortValue());
        Long long1 = Long.valueOf((long)integer.intValue() + 10L);
        System.out.println(integer.intValue() << 1);
        System.out.println((new StringBuilder()).append(integer).append("t").append(integer1).append("t").append(integer2).append("t").append(short1).append("t").append(integer3).append("t").append(long1).toString());

可以看到Integer num11 = num9 + num10; 這一句被劃分為3個步驟:將兩個Integer物件分別進行拆箱;將拆箱得到的兩個int數值相加求其和;將和值進行裝箱,從而將num11指向快取陣列中值為3的Integer物件。

而Short num12 = 5; 這一句則先將5強制轉換成short型別,再將其裝箱把值為5的Short物件的參照賦給num12。

而Integer num13 = num9 + num12; 這一句除了Integer num11 = num9 + num10;的3個步驟,中間還有short+int=int的型別自動提升的過程。

而Long num14 = num9 + 10L; 這一句Integer num11 = num9 + num10;的3個步驟,中間還有強制型別轉換的過程。需要注意的是,如果是Long num14 = num9 + num10; 的話就會出現型別不匹配的錯誤,因為num9、num10拆箱之後相加的和是int型別,而Long.valueOf(long)需要的形參是long型別,自然會出錯。我們也可以看到,當包裝型別物件和基本型別資料進行四則運算的時候,物件是會被拆箱的,然後再按基本型別資料的運算規則進行運算。

另外,如果僅僅是列印兩個包裝型別物件求和的結果,是不會有將和值重新轉換成該包裝型別的步驟的,如下面所示

System.out.println(num9 + num10);
System.out.println(integer.intValue() + integer1.intValue());

這裡還需要注意一點,儘管基本型別與自動型別提升/強制型別轉換,包裝類是沒有類似的用法的。下面的做法是錯的。

Short num3 = 10;
Integer num4 = num3;	//錯誤: 不相容的型別: Short無法轉換為Integer
Long num5 = (Long)num4;	//錯誤: 不相容的型別: Integer無法轉換為Long
Double num6 = num5;	//錯誤: 不相容的型別: Long無法轉換為Double

小結:不同包裝型別物件是不能直接轉換的,不過有兩種途徑可以代替:一種是上面討論的不同包裝類物件進行四則運算後賦給某一種型別;另一種就是利用包裝類的方法

Integer a = 20;
Long b = a.longValue();
Short c = b.shortValue();
System.out.println(a +"	"+ b +"	"+ c);

2、比較運算和邏輯運算

Integer num9 = 100;
Integer num10 = 200;
Short num11 = 50;
Long num12 = 50L;
System.out.println((num9<num10) +"	"+ (num9<200) +"	"+ (num9<num11) +"	"+ (num9<num12) +"	"+ (num9<10L));

反編譯結果為

Integer integer = Integer.valueOf(100);
Integer integer1 = Integer.valueOf(200);
Short short1 = Short.valueOf((short)50);
Long long1 = Long.valueOf(50L);
System.out.println((new StringBuilder()).append(integer.intValue() < integer1.intValue()).append("t").append(integer.intValue() < 200).append("t").append(integer.intValue() < short1.shortValue()).append("t").append((long)integer.intValue() < long1.longValue()).append("t").append((long)integer.intValue() < 10L).toString());

可以看到,兩個同型別的包裝類物件進行比較時比較的其實是各自的基本型別數值,如num9 < num10;兩個不同型別的包裝類物件進行比較時則在比較基本型別數值之前,會有型別提升or強制型別轉換,如num9 < num11,num9 < num12。

當想比較兩個物件是否相等時,注意要使用equals()方法,從前面的討論也知道,使用==的話比較的其實是參照的物件是否同一個,一般不滿足我們的需求。

Integer num13 = new Integer(100);
System.out.println(num9.equals(num13) +"	"+ num9.equals(50));

反編譯結果為

Integer integer2 = new Integer(100);
System.out.println((new StringBuilder()).append(integer.equals(integer2)).append("t").append(integer.equals(Integer.valueOf(50))).toString());

邏輯運算舉例:

System.out.println((num9&1));

反編譯結果為

System.out.println(integer.intValue() & 1);

五、包裝類作為方法的形參、返回值

//包裝類作為方法的形參、返回值
	public static Integer intToInteger(int i) {
		return i;
	}  
	public static int integerToInt(Integer i) {
		return i;
	}

反編譯結果為

    public static Integer intToInteger(int i)
    {
        return Integer.valueOf(i);
    }
 
    public static int integerToInt(Integer integer)
    {
        return integer.intValue();
    }

六、包裝類作為集合的元素

//包裝類作為集合元素
List list = new ArrayList();
list.add(1);
list.add(new Object());
Iterator it = list.iterator();
while (it.hasNext()) {
	System.out.println(it.next());
}

反編譯結果為

ArrayList arraylist = new ArrayList();
arraylist.add(Integer.valueOf(1));
arraylist.add(new Object());
for(Iterator iterator = arraylist.iterator(); iterator.hasNext(); System.out.println(iterator.next()));

可以發現,雖然集合元素要求是物件,add()方法的形參也是物件(public boolean add(E e)),但由於自動裝箱,基本資料型別也可以直接加入集合中。

                List<Integer> list = new ArrayList<>();
		for (int i=0; i<5; i++) {
			list.add(i);
		}
		Iterator it = list.iterator();
		while (it.hasNext()) {
			System.out.println(it.next());
		}

反編譯結果為

        ArrayList arraylist = new ArrayList();
        for(int i = 0; i < 5; i++)
            arraylist.add(Integer.valueOf(i));
 
        for(Iterator iterator = arraylist.iterator(); iterator.hasNext(); System.out.println(iterator.next()));

七、包裝類使用過程中有可能引起的空指標異常

//注意包裝類可能產生的空參照異常
		Boolean flag1 = false;
		System.out.println(flag1?"命題為真":"命題為假");
		Boolean flag2 = null;
		System.out.println(flag2?"命題為真":"命題為假");
		Boolean flag3 = true;

執行結果為

這裡只是簡單演示空指標異常。平時使用時需要注意這一點,比如當Boolean的物件作為形參時,在方法執行體的頭部需要做下null檢測。

上述程式碼的反編譯結果為

        Boolean boolean1 = Boolean.valueOf(false);
        System.out.println(boolean1.booleanValue() ? "u547Du9898u4E3Au771F" : "u547Du9898u4E3Au5047");
        Boolean boolean2 = null;
        System.out.println(boolean2.booleanValue() ? "u547Du9898u4E3Au771F" : "u547Du9898u4E3Au5047");
        Boolean boolean3 = Boolean.valueOf(true);

可見三目運運算元的條件表示式的位置一定是boolean值,如果你傳入的是Boolean物件,則會自動拆箱轉換為boolean值。

另外,三目運運算元的其他兩個表示式位置也是如此,會把包裝類物件轉換為相應的基本型別物件。

八、為什麼需要包裝類?有了包裝類又為什麼要保留基本資料型別?(包裝類的優缺點)

為什麼需要包裝類?

首先,Java語言是一個物件導向的語言,但是Java中的基本資料型別卻是不物件導向的,將每個基本資料型別設計一個對應的類進行代表,這種方式增強了Java物件導向的性質。

其次,如果僅僅有基本資料型別,那麼在實際使用時將存在很多的不便,很多地方都需要使用物件而不是基本資料型別。比如,在集合類中,我們是無法將int 、double等型別放進去的,因為集合的容器要求元素是Object型別。而包裝型別的存在使得向集合中傳入數值成為可能,包裝類的存在彌補了基本資料型別的不足。

此外,包裝類還為基本型別新增了屬性和方法,豐富了基本型別的操作。如當我們想知道int取值範圍的最小值,我們需要通過運算,如下面所示,但是有了包裝類,我們可以直接使用Integer.MAX_VALUE即可。

//求int的最大值
int max = 0;
int flag = 1;
for (int i=0; i<31; i++) {
	max += flag;
	flag = flag << 1;
}
System.out.println(max +"	"+ Integer.MAX_VALUE); //2147483647      2147483647

為什麼要保留基本資料型別?

我們都知道在Java語言中,用new關鍵字建立的物件是儲存在堆裡的,我們通過棧中的參照來使用這些物件,所以,物件本身來說是比較消耗資源的。對於經常用到的型別,如int等,如果我們每次使用這種變數的時候都需要new一個物件的話,就會比較笨重了。所以,Java提供了基本資料型別,這種資料的變數不需要使用new在堆上建立,而是直接在棧記憶體中儲存,因此會更加高效。

到此這篇關於Java包裝類的文章就介紹到這了,更多相關Java包裝類內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com