寫在前面:
三目運算子是我們經常在程式碼中使用的,
a= (b==null?0:1);
這樣一行程式碼可以代替一個if-else,
可以使程式碼變得清爽易讀。但是,三目運算子也是有一定的語言規範的。在運用不恰當的時候會導致意想不到的問題。前段時間遇到(一個由於使用三目運算子導致的問題,其實是因為有三目運算子和自動拆箱同時使用(雖然自動拆箱不是我主動用的)。
一、三目運算子
對於條件表示式b?x:y
,先計算條件b,然後進行判斷。如果b的值為true,計算x的值,運算結果為x的值;否則,計算y的值,運算結果為y的值。一個條件表示式從不會既計算x,又計算y。條件運算子是右結合的,也就是說,從右向左分組計算。例如,a?b:c?d:e將按a?b:(c?d:e)執行。
二、自動裝箱與自動拆箱
基本資料型別的自動裝箱(autoboxing
)、拆箱(unboxing
)是自J2SE 5.0開始提供的功能。 一般我們要建立一個類的物件例項的時候,我們會這樣: Class a = new Class(parameters);
當我們建立一個Integer
物件時,卻可以這樣: Integer i = 100;
(注意:和 int i = 100
;是有區別的 ) 實際上,執行上面那句程式碼的時候,系統為我們執行了: Integer i = Integer.valueOf(100)
; 這裡暫且不討論這個原理是怎麼實現的(何時拆箱、何時裝箱),也略過普通資料型別和物件型別的區別。我們可以理解為,當我們自己寫的程式碼符合裝(拆)箱規範的時候,編譯器就會自動幫我們拆(裝)箱。那麼,這種不被程式設計師控制的自動拆(裝)箱會不會存在什麼問題呢?
三、問題回顧
首先,通過你已有的經驗看一下下面這段程式碼。如果你得到的結果和後文分析的結果一致(並且你知道原理),那麼請忽略本文。如果不一致,請跟我探索下去。
1 2 |
Map<String,Boolean> map = new HashMap<String, Boolean>(); Boolean b = (map!=null ? map.get("test") : false); |
以上這段程式碼,是我們在不注意的情況下有可能經常會寫的一類程式碼(在很多時候我們都愛使用三目運算子)。當然,這段程式碼是存在問題的,執行該程式碼,會報NPE.
1 |
Exception in thread "main" java.lang.NullPointerException |
首先可以明確的是,既然報了空指標,那麼一定是有些地方呼叫了一個null的物件的某些方法。在這短短的兩行程式碼中,看上去只有一處方法呼叫map.get("test")
,但是我們也都是知道,map已經事先初始化過了,不會是Null,那麼到底是哪裡有空指標呢。我們接下來反編譯一下該程式碼。看看我們寫的程式碼在經過編譯器處理之後變成了什麼樣。
反編譯後程式碼如下:
1 2 |
HashMap hashmap = new HashMap(); Boolean boolean1 = Boolean.valueOf(hashmap == null ? false : ((Boolean)hashmap.get("test")).booleanValue()); |
看完這段反編譯之後的程式碼之後,經過分析我們大概可以知道問題出在哪裡。((Boolean)hashmap.get("test")).booleanValue()
的執行過程及結果如下:
hashmap.get(“test”)->null;
(Boolean)null->null;
null.booleanValue()->報錯
好,問題終於定位到了。那麼接下來看看如何解決該問題以及為什麼會出現這種問題。
四、原理分析
通過檢視反編譯之後的程式碼,我們準確的定位到了問題,分析之後我們可以得出這樣的結論:NPE的原因應該是三目運算子和自動拆箱導致了空指標異常。
那麼,這段程式碼為什麼會自動拆箱呢?這其實是三目運算子的語法規範。參見jls-15.25,摘要如下:
If the second and third operands have the same type (which may be the null type), then that is the type of the conditional expression.
If one of the second and third operands is of primitive type T, and the type of the other is the result of applying boxing conversion (§5.1.7) to T, then the type of the conditional expression is T.
If one of the second and third operands is of the null type and the type of the other is a reference type, then the type of the conditional expression is that reference type.
簡單的來說就是:當第二,第三位運算元分別為基本型別和物件時,其中的物件就會拆箱為基本型別進行操作。
所以,結果就是:由於使用了三目運算子,並且第二、第三位運算元分別是基本型別和物件。所以對物件進行拆箱操作,由於該物件為null,所以在拆箱過程中呼叫null.booleanValue()的時候就報了NPE。
五、問題解決
如果程式碼這麼寫,就不會報錯:
1 2 |
Map<String,Boolean> map = new HashMap<String, Boolean>(); Boolean b = (map!=null ? map.get("test") : Boolean.FALSE); |
就是保證了三目運算子的第二第三位運算元都為物件型別。
這和三目運算子有關。