包裝類型是一個類,它提供了諸如構造方法、類型轉換、比較等非常實用的功能,而且在Java 5之後又實現了與基本類型之間的自動轉換,這使包裝類型如虎添翼,更是應用廣泛了,在開發中包裝類型已經隨處可見,但無論是從安全性、性能方面來說,還是從穩定性方面來說,基本類型都是首選方案。我們來看一段代碼:
public class Client{
public static void main(Stringargs){
Client cilent=new Client();
int i=140;
//分別傳遞int類型和Integer類型
cilent.f(i);
cilent.f(Integer.valueOf(i));
}
public void f(long a){
System.out.println("基本類型的方法被調用");
}
public void f(Long a){
System.out.println("包裝類型的方法被調用");
}
}
在上面的程序中首先聲明了一個int變量i,然後加寬轉變成long型,再調用f()方法,分別傳遞int和long的基本類型和包裝類型,諸位想想該程序是否能夠編譯?如果能編譯輸出結果又是什麼呢?
首先,這段程序絕對是能夠編譯的。不過,說不能編譯的同學還是很動了一番腦筋的,只是還欠缺點火候,你可能會猜測以下這些地方不能編譯:
f()方法重載有問題。定義的兩個f()方法實現了重載,一個形參是基本類型,一個形參是包裝類型,這類重載很正常。雖然基本類型和包裝類型有自動裝箱、自動拆箱的功能,但並不影響它們的重載,自動拆箱(裝箱)只有在賦值時才會發生,和重載沒有關係。
cilent. f(i)報錯。i是int類型,傳遞到fun(long l)是沒有任何問題的,編譯器會自動把i的類型加寬,並將其轉變為long型,這是基本類型的轉換規則,也沒有任何問題。
cilent. f(Integer.valueOf(i))報錯。代碼中沒有f(Integer i)方法,不可能接收一個Integer類型的參數,而且Integer和Long兩個包裝類型是兄弟關係,不是繼承關係,那就是說肯定編譯失敗了?不,編譯是成功的,稍後再解釋為什麼這裡編譯成功。
既然編譯通過了,我們來看一下輸出:
基本類型的方法被調用
基本類型的方法被調用
cilent. f(i)的輸出是正常的,我們已經解釋過了。那第二個輸出就讓人很困惑了,為什麼會調用f(long a)方法呢?這是因為自動裝箱有一個重要的原則:基本類型可以先加寬,再轉變成寬類型的包裝類型,但不能直接轉變成寬類型的包裝類型。這句話比較拗口,簡單地說就是,int可以加寬轉變成long,然後再轉變成Long對象,但不能直接轉變成包裝類型,注意這裡指的都是自動轉換,不是通過構造函數生成。為了解釋這個原則,我們再來看一個例子:
public class Client{
public static void main(Stringargs){
int i=100;
f(i);
}
public static void f(Long l){
}
}
這段程序編譯是通不過的,因為i是一個int類型,不能自動轉變為Long型。但是修改成以下代碼就可以編譯通過了:
public static void main(Stringargs){
int i=100;
long l=(long)i;
f(l);
}
這就是int先加寬轉變為long型,然後自動轉換成Long型。規則說明白了,我們繼續來看f(Integer.valueOf(i))是如何調用的,Integer.valueOf(i)返回的是一個Integer對象,這沒錯,但是Integer和int是可以互相轉換的。沒有f(Integer i)方法?沒關係,編譯器會嘗試轉換成int類型的實參調用,OK,這次成功了,與f(i)相同了,於是乎被加寬轉變成long型——結果也很明顯了。整個f(Integer.valueOf(i))的執行過程是這樣的:
i通過valueOf方法包裝成一個Integer對象。
由於沒有f(Integer i)方法,編譯器「聰明」地把Integer對像轉換成int。
int自動拓寬為long,編譯結束。
使用包裝類型確實有方便的地方,但是也會引起一些不必要的困惑,比如我們這個例子,如果f()的兩個重載方法使用的是基本類型,而且實參也是基本類型,就不會產生以上問題,而且程序的可讀性更強。自動裝箱(拆箱)雖然很方便,但引起的問題也非常嚴重——我們甚至都不知道執行的是哪個方法。
注意 重申,基本類型優先考慮。