靜態分派(Method Overload Resolution)
所有依賴靜態類型來定位方法執行版本的分派動作稱爲靜態分派。典型應用方法是重載。
靜態分派發生在編譯階段,因此確定靜態分派動作是由編譯器來完成的,在很多情況下,重載版本並不是唯一的,而是“當前最合適的”版本。
舉一個靜態分派的極端例子:
上面的代碼輸出hello char。’a‘是一個char類型,會自然尋找char的重載方法。
但如果註釋掉char方法,則輸出變爲:
hello int。此時發生了一次自動類型轉換,'a'除了可以代表字符串,也可以代表數字97(unicode數值)。
如果繼續註釋掉int方法,則輸出會變爲:
hello long。
這時發生了兩次自動類型轉換,'a'轉型爲整數97後,進一步轉型爲97L,重載了long類型方法。
事實上重載還能繼續發生很多次,按照char>int>long>float>double進行,但不會匹配到byte和short,因爲轉型不安全。
如果繼續註釋掉long,則輸出會變爲:
hello Character。
這時發生了一次自動裝箱。'a'被包裝爲它的封裝類型java.lang.Character,匹配到Character的重載。
繼續註釋掉Character方法,輸出會變爲:
hello Serializable。
因爲java.lang.Serializable是java.lang.Character實現的一個接口。
當自動裝箱後還找不到裝箱類,但是找到了裝箱類實現了的接口類型,所以又發生一次自動轉型。
char可以轉型爲int,但Character絕對不會轉型爲Integer。它只能安全地轉型爲它實現的接口或父類。
Character還實現了另外一個java.lang.Comparable<Character>如果同時出現,他們的優先級是一樣的。編譯器無法確定時會提示類型模糊,拒絕編譯。
繼續註釋掉Serializable,則輸出會變爲:
hello Object
這時是char裝箱後轉型爲父類了。
再次註釋後,輸出變爲:
hello char...
變長參數重載優先級最低。
包裝類型
Java語言是一個面向對象的語言,雖然每一個引用類型都和Object相容,但8種基本類型是不能的,這在實際使用時存在很多的不便。於是,Java爲這8種基本類型提供了包裝類。(Wrapper Class)。
每一個包裝對象都是不可變的(狀態絕不能改變,即不能被類型轉換,見下文類型轉換例子)
包裝類均位於java.lang包,包裝類和基本數據類型的對應關係如下表所示:
基本數據類型 | 包裝類 |
byte | Byte |
double | Double |
short | Short |
char | Character |
int | Integer |
long | Long |
float | Float |
double | Double |
爲什麼需要包裝類?
比如,在集合類中,我們是無法將int 、double等類型放進去的。因爲集合的容器要求元素是Object類型。
爲了讓基本類型也具有對象的特徵,就出現了包裝類型,它相當於將基本類型“包裝起來”,使得它具有了對象的性質,並且爲其添加了屬性和方法,豐富了基本類型的操作。
自動拆箱與裝箱
舉例來說:
- 如果一個int型量被傳遞到一個需要Integer對象的地方,那麼編譯器將在幕後插入一個對Integer構造方法的調用,這就叫做自動裝箱。
- 如果一個Integer對象被放到需要int型的地方,則編譯器會插入一個intValue方法的調用,這叫自動拆箱。
當表格中基礎類型與它們的包裝類有如下幾種情況時,編譯器會自動幫我們進行裝箱或拆箱.
- 進行 = 賦值操作(裝箱或拆箱)
- 進行+,-,*,/混合運算 (拆箱)
- 進行>,<,==比較運算(拆箱)
- 調用equals進行比較(裝箱)
- ArrayList,HashMap等集合類 添加基礎類型數據時(裝箱)
例如:
一個典型的自動拆裝箱示例
public static void main(String[]args){
Integer integer=1; //裝箱
int i=integer; //拆箱
}
反編譯後:
public static void main(String[]args){
Integer integer=Integer.valueOf(1);
int i=integer.intValue();
}
自動裝箱是通過包裝類的valueOf()
方法來實現的.自動拆箱都是通過包裝類對象的xxxValue()
來實現的。
自動拆裝箱可能產生的問題:
- 用一個例子來解釋自動裝箱和包裝類的類型轉換限制(包裝類不可變原則):
我們設置一個類,其變量可以被設置爲Object的任意類型:
編寫一個測試類:
在這個例子中,MemoryCell的實例m,先設置了一個String類型的變量,可以正常讀取和打印。
然後,寫入一個int類型123時,報了類型轉換ClassCastException。
這裏發生了兩個操作:
1.由於m是Object泛型實例,write時,調用了自動裝箱操作,將123轉換爲Integer類型,寫入操作正常。
2.由於包裝類型是不能互相轉換的,在(String)強制轉換過程中,拋出類型轉換異常。
- Integer cache的上下限問題:
1.基礎類型a與包裝類b進行==比較,這時b會拆箱,直接比較值,所以會打印true。
2.二個包裝類型,都被賦值了100,所以根據我們之前的解析,這時會進行裝箱,調用Integer的valueOf方法,生成2個Integer對象,引用類型==比較,直接比較對象指針,這裏我們先給出結論,最後會分析原因,打印 true。
3.跟上面第2段代碼類似,只不過賦值變成了200,打印 false。
默認Integer cache 的下限是-128,上限默認127,可以配置,所以到這裏就清楚了,我們上面當賦值100給Integer時,剛好在這個range內,所以從cache中取對應的Integer並返回,所以二次返回的是同一個對象,所以==比較是相等的,當賦值200給Integer時,不在cache 的範圍內,所以會new Integer並返回,當然==比較的結果是不相等的。
包裝對象的數值比較,不能簡單的使用==,雖然-128到127之間的數字可以,但是這個範圍之外還是需要使用equals比較。
- 小心空指針異常
有這麼一段代碼:
1 2 3 4 5 6 7 8 9 10 |
|
如果運行的話:
1 2 |
|
這種使用場景很常見,我們把一個int數值放在session或者request中,取出來的時候就是一個類似上面的場景了。所以,小心自動拆箱時候的空指針異常。
有些場景會進行自動拆裝箱,同時也說過,由於自動拆箱,如果包裝類對象爲null,那麼自動拆箱時就有可能拋出NPE。
另外的例子參見:Java中的三目運算符及拆裝箱產生的異常