1. 程式人生 > >Java中的泛型和裝箱和拆箱

Java中的泛型和裝箱和拆箱

對於泛型和基本資料型別的裝箱和拆箱大家都很瞭解。

我就簡單說一下。

    1、泛型是JDK1.5的一項新增特性,它的本質是引數化型別的應用,也就是說所操作的資料型別被指定為一個引數。這種引數可以用在類、介面和方法的建立中,分別稱為泛型類、泛型介面和泛型方法。

    Java中的泛型只存在於程式原始碼中,在編譯的位元組碼檔案中,就已經替換為原來的原生型別,並且在相應的地方插入了強制轉換。因此,對於執行期的Java語言,ArrayList<Integer>和ArrayList<String>就是同一個類。

    我們看一段簡單的Java泛型的例子。

public static void 
main(String[] args) { Map<String,String> map = new HashMap<String,String>(); map.put("hello","111"); map.put("how are you?","222"); System.out.println(map.get("hello")); System.out.println(map.get("how are you?")); }

    把這段程式碼編譯成Class檔案,然後經過位元組碼反編譯工具進行反編譯後,將會發現泛型都不見了

public static void 
main(String[] args) { Map map = new HashMap(); map.put("hello","111"); map.put("how are you?","222"); System.out.println(map.get("hello")); System.out.println(map.get("how are you?")); }

    這就是泛型在編譯期的擦除。泛型擦除只在編譯階段有效。在編譯過程中,正確檢驗泛型結果後,會將泛型的相關資訊擦除,並且在物件進入和離開方法的邊界處新增型別檢查和型別轉換的方法。

   相信你看上面的內容後,就知道下面程式碼的輸出結果。

public static void main(String[] args) {
    Class c1 = new ArrayList<Integer>().getClass();
Class c2 = new ArrayList<String>().getClass();
System.out.println(c1==c2);
}

    沒錯,就是true。因為在編譯期生成的只有ArrayList類,而它的的Class檔案必然只有一個,所以返回true。

    當然,泛型擦除也有不足的地方,就比如當泛型遇上過載

public void f(List<String> list){
    System.out.println(list);
}

public void f(List<Integer> list){
    System.out.println(list);
}
    這樣的程式碼通過不了編譯,因為編譯期將泛型擦除後,它們的引數都是ArrayList,導致兩個方法的引數簽名一模一樣,無法解決歧義。

    2、自動裝箱、拆箱

        什麼是裝箱和拆箱?

            大家都熟悉基本型別資料和包裝型別資料吧。

            在JavaSE 1.5提供了自動裝箱和拆箱,比如我們要建立一個數值為10的Integer物件,可以這樣

            Integer i = 10;

            這便是自動裝箱,在這個過程中會自動根據數值建立Integer物件。

            而int j = i, 便是自動拆箱,就是將包裝型別轉換為基本資料型別。

        裝箱和拆箱的實現

            看一段程式碼

public static void main(String[] args) {
    Integer i = 10;
    int j = i;
System.out.println(j);
}
           反編譯之後    


        可以看到,給物件i賦值時,使用了Integer.valueOf方法,而在給變數j賦值時使用了Integer.intValue方法。

    在裝箱的過程中是有幾個坑要注意的,比如在下面的程式碼中可以看到

public static void main(String[] args) {
    Integer i = 100;
Integer j = 100;
Integer m = 200;
Integer n = 200;
System.out.println(i==j);
System.out.println(m==n);
}
      輸出為true和false,為什麼呢,我們可以進入原始碼看

        

    它是有一個緩衝池,在-128到127之間,這之間的數並沒有直接建立新的Integer物件,而是直接從快取池中拿的。所以他們是相同的引用,用==比較時都是返回true,但是這些數之外的就是新建立的Integer物件,不可能是相同的。

    但是Double就不一樣的,看程式碼

public static void main(String[] args) {
    Double i = 100.0;
Double j = 100.0;
Double m = 200.0;
Double n = 200.0;
System.out.println(i==j);
System.out.println(m==n);
}

    它的輸出卻為兩個false。

    還有你能說出Integer i = new Integer(x)和Integer i = x的區別嗎?

        第一種不會觸發自動裝箱,而第二種會觸發自動裝箱

        第二種在一般情況下的執行效率和空間佔用效率比第一種優

    最後再出一個題考考吧 

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;
System.out.println(c==d);
System.out.println(e==f);
System.out.println(c==(a+b));
System.out.println(c.equals(a+b));
System.out.println(g==(a+b));
System.out.println(g.equals(a+b));
}

        第一個和第二個沒什麼說的,第三個用到了關係操作符+,所以觸發了自動拆箱,因此比較的是值的大小,返回true;

        第四個用到了操作符+和equals,所以先拆箱後裝箱,比較的也是值的大小,所以返回true;第五個用到了操作符+,進行

        自動拆箱後,比較的是值本身,返回true。最後一個用到了操作符+和equals方法,先拆箱後裝箱,不過拆箱的時候用的是intValue方法,返回為int,裝箱之後也是Integer,而Long的equals方法只能和Long進行比較,其餘都返回false。