Java包裝類

2.1 包裝類的作用

(1)基本數據類型的存在意義

  我們都知道在Java語言中,new一個對象存儲在堆裏,我們通過棧中的引用來使用這些對象。但是對於經常用到的一系列類型如int、boolean…如果我們用new將其存儲在堆裏就不是很高效——特別是簡單的小的變量。所以,同C++一樣Java也採用了相似的做法,決定基本數據類型不是用new關鍵字來創建,而是直接將變量的值存儲在棧中,方法執行時創建,結束時銷燬,因此更加高效。所以基本數據類型的存在意義是爲了運行效率

(2)爲什麼要引入包裝類

  Java是一門面向對象的編程語言,但是Java中的基本數據類型卻不是面向對象的,並不具有對象的性質,這在實際生活中存在很多的不便。爲了讓基本類型也具有對象的特徵,就出現了包裝類型,它相當於將基本類型“包裝起來”,使得具有了面向對象的性質,並且爲其添加了屬性和方法,豐富了基本類型的操作,方便涉及到對象的操作。所以,引入包裝類是爲了方便對基本類型進行操作

  在使用集合類型時,就一定要使用包裝類型,因爲容器都是裝object的,基本數據類型顯然不適用。

2.2 包裝類的原理

2.2.1 包裝類

  Java設計當初就提供類8種基本數據類型及對應的8 種包裝類(封裝類);

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

2.2.2 裝箱和拆箱

 下面均以int,Integer爲例進行講解;

  • 裝箱:基本數據類型轉換爲包裝類;裝箱有兩種方法:
//裝箱兩種方法
Integer i1 = new Integer(8);
Integer i2 = Integer.valueOf(8);

// 自動裝箱,會隱式調用valueOf方法
Integer i3 = 8;
fun(8);

void fun( Integer a){
    //...
}
  • 拆箱:包裝類轉換爲基本數據類型;拆箱方法:
//拆箱方法
int i5 = i3.intValue();

//自動拆箱,會隱式調用intValue方法
int i4 = i3;
test(i3);
void test(int a){
    //...
}

2.2.3 緩存池

 上一節說到,裝箱有兩種方法:new Integer(8)Integer.valueOf(8);他們的區別在於:

  • new Integer(8)每次調用創建一個新對象;
  • Integer.valueOf(8)使用緩存池中的對象,多次調用會取得同一個對象的引用(可複用)。

 下面看一下Integer的源碼,分析一下valueOf和緩存池;

 valueOf() 方法的實現比較簡單,就是先判斷值是否在緩存池中,如果在的話就直接返回緩存池的內容。

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];//1
    return new Integer(i);//2
}
Integer i6 = 127;//自動裝箱,值在緩存池範圍內,調用valueOf,執行1,i6是緩存池單元的引用(地址);
Integer i7 = 128;//自動裝箱,值在緩存池範圍外,調用valueOf,執行2,最終調用newInteger,i7是堆單元的引用;

 在 Java 8 中,Integer 緩存池的大小默認爲 -128~127,和byte範圍相同;

    private static class IntegerCache {
        static final int low = -128;//Integer緩存池最小值
        static final int high;//Integer緩存池最大值
        static final Integer cache[];

        static {
            // high value may be configured by property
            int h = 127;//Integer緩存池最大值
            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;//Integer緩存池最大值
            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;
        }
        private IntegerCache() {}
    }

2.3.4 封裝類對象的比較

封裝類對象的比較強制使用equals(源自阿里編碼規範);

public class Test {  
    public static void main(String[] args) {  
        Integer i1 = new Integer(127);
        Integer i2 = Integer.valueOf(127);
        Integer i3 = 127;  
        Integer i4 = 127;  
        Integer i5 = 128;  
        Integer i6 = 128;  
        int i7 = 127;
        System.Out.println(i1 == i2); //1false;
        System.Out.println(i2 == i3); //2true
        System.Out.println(i3 == i4); //3true
        System.Out.println(i5 == i6); //4false 
        System.Out.println(i5.equals(i6)); //5true 
        System.Out.println(i1 == i7);//true
        System.Out.println(i3 == i7);//true
    }  
} 

 首先聲明==和equals的區別:

  • equals比較對象的內容(所有屬性值);
  • ==如果是基本數據類型比較值,如果是對象比較內存地址;

 (1)i1是堆中新開闢一塊內存的地址;i2是堆中緩存池某一單元的地址;1輸出false;

 (2)自動裝箱,值在緩存池範圍內,調用valueOf,i2、i3和i4是同一緩存池單元的引用(地址);2,3輸出true;

 (3)自動裝箱,值在緩存池範圍外,valueOf最終調用newInteger,i5,i6都創建了一個新對象,倆對象內容相同,地址不同;4false ,5true ;

 (3) System.Out.println(i1 == i7);編譯成 System.Out.println(i1.intValue() == i7);所以輸出true;System.Out.println(i3 == i7);同理輸出true;

2.2.5 String和Integer相互轉換

  • String to Integer: Intrger.parseInt(string);
  • Integer to String: Integer.toString();

2.3 基本類型和包裝類的區別

  • 存儲位置:基本類型變量值存儲在棧中,而包裝類型是將對象放存儲在堆中,然後通過引用來使用;

  • 默認初值:基本類型的默認初始如int爲0,boolean爲false,而包裝類型的默認初始爲null;

  • 作用不同:基本數據類型的存在意義是爲了運行效率;而引入包裝類是爲了方便對基本類型進行操作

2.3 包裝類使用場景

  • 泛型(包括容器的泛型參數)只能使用包裝類;
  • 某個允許爲null的屬性只能使用包裝類;

 以下三條源自阿里編碼規範:

  • 所有POJO類(簡單Java類,只有屬性、構造器、seter、geter的類)屬性;
  • RPC(遠程方法調用)方法返回值和參數必須使用包裝數據類型;
  • 推薦所有的局部變量使用基本數據類型,避免頻繁GC;

  ps:在一個ORM中存在一個Student類和Student類表,有一個JavaBean 類Student類中有一個表示學生考試分數的屬性score,相對應Student類表中有一個表示學生考試分數的字段score;如果用Integer而不用int定義這個屬性,一次考試,學生可能沒考,值是null,也可能考了,但考了0分,值是0,這兩個表達的狀態明顯不一樣

2.4 包裝類存在的問題

  • 效率低;

  由於使用包裝類會創建新對象,而且若在循環中使用包裝類會頻繁GC,效率比較低;所以在循環中儘量使用基本數據類型;所以阿里編碼規範推薦所有的局部變量使用基本數據類型;

Integer sum = 0;
 for(int i=0; i<1000; i++){
   sum+=i;
}
  • 拆箱可能會產生空指針異常;
Object obj = null;//1
int i = (Integer)obj;//2

  2處自動拆箱會調用obj.intValue();方法,從而產生空指針異常;

參考:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章