JAVA 三目運算時遇到的坑

三目運算符是咱們常常在代碼中使用的,a= (b==null?0:1);這樣一行代碼能夠代替一個if-else,可使代碼變得清爽易讀。java

可是,三目運算符也是有必定的語言規範的。在運用不恰當的時候會致使意想不到的問題。本文就介紹一個我本身曾經踩過的坑。程序員

1、三目運算符express

對於條件表達式b?x:y,先計算條件b,而後進行判斷。若是b的值爲true,計算x的值,運算結果爲x的值;不然,計算y的值,運算結果爲y的值。一個條件表達式從不會既計算x,又計算y。條件運算符是右結合的,也就是說,從右向左分組計算。例如,a?b:c?d:e將按a?b:(c?d:e)執行。app

2、自動裝箱與自動拆箱指針

基本數據類型的自動裝箱(autoboxing)、拆箱(unboxing)是自J2SE 5.0開始提供的功能。code

通常咱們要建立一個類的對象實例的時候,咱們會這樣: Class a = new Class(parameters); 當咱們建立一個Integer對象時,卻能夠這樣: Integer i = 100;(注意:和 int i = 100;是有區別的 )對象

實際上,執行上面那句代碼的時候,系統爲咱們執行了: Integer i = Integer.valueOf(100); 這裏暫且不討論這個原理是怎麼實現的(什麼時候拆箱、什麼時候裝箱),也略過普通數據類型和對象類型的區別。get

咱們能夠理解爲,當咱們本身寫的代碼符合裝(拆)箱規範的時候,編譯器就會自動幫咱們拆(裝)箱。那麼,這種不被程序員控制的自動拆(裝)箱會不會存在什麼問題呢?編譯器

3、問題回顧hash

首先,經過你已有的經驗看一下下面這段代碼。若是你獲得的結果和後文分析的結果一致(而且你知道原理),那麼請忽略本文。若是不一致,請跟我探索下去。

public static void main(String[] args) {    
    Map<String, Boolean> map = new HashMap<>();    
    Boolean b = map != null ? map.get("test") : false;    
    System.out.println(b);
}

以上這段代碼,是咱們在不注意的狀況下有可能常常會寫的一類代碼(在不少時候咱們都愛使用三目運算符)。

通常狀況下,咱們會認爲以上代碼Boolean b的最終獲得的值應該是null。由於map.get("test")的值是null,而b又是一個對象,因此獲得結果會是null。

可是,以上代碼會拋出NPE:

Exception in thread "main" java.lang.NullPointerException
首先能夠明確的是,既然報了空指針,那麼必定是有些地方調用了一個null的對象的某些方法。在這短短的兩行代碼中,看上去只有一處方法調用map.get("test"),可是咱們也都是知道,map已經事先初始化過了,不會是Null,那麼究竟是哪裏有空指針呢。

咱們接下來[反編譯]一下該代碼。看看咱們寫的代碼在通過編譯器處理以後變成了什麼樣。反編譯後代碼以下:

public static void main(String args[]){   
    Map map = new HashMap();   
    Boolean b = Boolean.valueOf(map == null ? false : ((Boolean)map.get("test")).booleanValue());   
    System.out.println(b);
}

看完這段反編譯以後的代碼以後,通過分析咱們大概能夠知道問題出在哪裏。((Boolean)hashmap.get("test")).booleanValue() 的執行過程及結果以下:

hashmap.get("test")->null;

(Boolean)null->null;

null.booleanValue()->報錯

好,問題終於定位到了。很明顯,上面源代碼中的map.get("test")在被編譯成了

(Boolean)map.get("test").booleanValue(),這是一種自動拆箱的操做。

那麼,爲何這裏會發生自動拆箱呢?這個問題又如何解決呢?

4、原理分析

經過查看反編譯以後的代碼,咱們準確的定位到了問題,分析以後咱們能夠得出這樣的結論: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.

簡單的來講就是:當第二,第三位操做數分別爲基本類型和對象時,其中的對象就會拆箱爲基本類型進行操做。

因此,結果就是:因爲使用了三目運算符,而且第2、第三位操做數分別是基本類型和對象。因此對對象進行拆箱操做,因爲該對象爲null,因此在拆箱過程當中調用null.booleanValue()的時候就報了NPE。

5、問題解決

若是代碼這麼寫,就不會報錯:

Map<String,Boolean> map =  new HashMap<String, Boolean>();
Boolean b = (map!=null ? map.get("test") : Boolean.FALSE);

就是保證了三目運算符的第二第三位操做數都爲對象類型。這樣就不會發生自動拆箱操做,以上代碼獲得的b的結果爲null。

PS:本文中的示例,只是爲了更加方便讀者理解三目運算符會致使自動拆箱現象,可能在代碼中並不會直接這樣使用。可是,我本身的代碼確實發生過相似問題。這裏簡化一下,爲了講清楚原理。

相關文章
相關標籤/搜索