java靜態分派+包裝類+自動裝箱拆箱

靜態分派(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方法的調用,這叫自動拆箱。

當表格中基礎類型與它們的包裝類有如下幾種情況時,編譯器會自動幫我們進行裝箱或拆箱.

  1. 進行 = 賦值操作(裝箱或拆箱)
  2. 進行+,-,*,/混合運算 (拆箱)
  3. 進行>,<,==比較運算(拆箱)
  4. 調用equals進行比較(裝箱)
  5. 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

public static void main(String[] args) throws Exception

{

    Object obj = getObj(null);

    int i = (Integer)obj;

}

 

public static Object getObj(Object obj)

{

    return obj;

}

如果運行的話:

1

2

Exception in thread "main" java.lang.NullPointerException

    at main.Test7.main(Test7.java:8)

這種使用場景很常見,我們把一個int數值放在session或者request中,取出來的時候就是一個類似上面的場景了。所以,小心自動拆箱時候的空指針異常。

 

有些場景會進行自動拆裝箱,同時也說過,由於自動拆箱,如果包裝類對象爲null,那麼自動拆箱時就有可能拋出NPE。

另外的例子參見:Java中的三目運算符及拆裝箱產生的異常

 

 

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