三目運算符NPE

複合三目運算符問題:
a?b:c?d:e
條件運算符是右結合的,也就是說,從右向左分組計算。例如,a?b:c?d:e 將按
a?b:(c?d:e)執行,從右向左計算,先(c?d:e)再a?b:(c?d:e)。
注意使用過程中,引起的自動拆箱引起的NPE異常:
當第二位和第三位表達式都是包裝類型的時候,該表達式的結果纔是該包裝類型,否則,只要有一個表達式的類型是基本數據類型,則表達式得到的結果都是基本數據類型。如果結果不符合預期,那麼編譯器就會進行自動拆箱。

boolean flag = true;
 boolean simpleBoolean = false;
 Boolean objectBoolean = Boolean.FALSE;

 // 當第二位和第三位表達式都是對象時,表達式返回值也爲對象;
 Boolean x1 = flag ? objectBoolean : objectBoolean;
 // 反編譯後代碼爲:Boolean x1 = flag ? objectBoolean : objectBoolean;
 // 因爲 x1 的類型是對象,所以不需要做任何特殊操作。

 // 當第二位和第三位表達式都爲基本類型時,表達式返回值也爲基本類型;
 boolean x2 = flag ? simpleBoolean : simpleBoolean;
 // 反編譯後代碼爲:boolean x2 = flag ? simpleBoolean : simpleBoolean;
 // 因爲 x2 的類型也是基本類型,所以不需要做任何特殊操作。
 // 當第二位和第三位表達式中有一個爲基本類型時,表達式返回值也爲基本類型;
 boolean x3 = flag ? objectBoolean : simpleBoolean;
 // 反編譯後代碼爲:boolean x3 = flag ? objectBoolean.booleanValue() : simpleBoolean;
 // 因爲 x3 的類型是基本類型,所以需要對其中的包裝類進行拆箱。

但是,並不是所有人都熟知這個規則,所以在實際應用中,還會出現以下三種定
義方式:

// 當第二位和第三位表達式都是對象時,表達式返回值也爲對象;
 boolean x4 = flag ? objectBoolean : objectBoolean;
 // 反編譯後代碼爲:boolean x4 = (flag ? objectBoolean : objectBoolean).
booleanValue();
 // 因爲 x4 的類型是基本類型,所以需要對表達式結果進行自動拆箱。

 // 當第二位和第三位表達式都爲基本類型時,表達式返回值也爲基本類型;
 Boolean x5 = flag ? simpleBoolean : simpleBoolean;
 // 反編譯後代碼爲:Boolean x5 = Boolean.valueOf(flag ? simpleBoolean :
simpleBoolean);
 // 因爲 x5 的類型是對象類型,所以需要對表達式結果進行自動裝箱。

 // 當第二位和第三位表達式中有一個爲基本類型時,表達式返回值也爲基本類型;
 Boolean x6 = flag ? objectBoolean : simpleBoolean;
 // 反編譯後代碼爲:Boolean x6 = Boolean.valueOf(flag ? objectBoolean.
booleanValue() : simpleBoolean);
 // 因爲 x6 的類型是對象類型,所以需要對表達式結果進行自動裝箱。

在開發過程中,如果涉及到三目運算符,那麼就要高度注意其中的自動拆裝箱問題。

最好的做法就是保持三目運算符的第二位和第三位表達式的類型一致,並且如果
要把三目運算符表達式給變量賦值的時候,也儘量保持變量的類型和他們保持一致。
並且,做好單元測試!!!
如果一定要給出一個方法論去避免這個問題的話,那麼在使用的過程中,無論是
三目運算符中的三個表達式,還是三目運算符表達式要賦值的變量,最好都使用包裝
類型,可以減少發生錯誤的概率。

Map<String,Boolean> map = new HashMap<String, Boolean>();
 Boolean b = (map!=null ? map.get(“Hollis”) : false);

以上代碼,在小於 JDK 1.8 的版本中執行的結果是 NPE,在 JDK 1.8 及
以後的版本中執行結果是 null。
說下IDK8中
JLS(java規範) 15 中對條件表達式(三目運算符)做了細分之後分爲三種,區分方式:
● 如果表達式的第二個和第三個操作數都是布爾表達式,那麼該條件表達式就是
布爾表達式
● 如果表達式的第二個和第三個操作數都是數字型表達式,那麼該條件表達式就
是數字型表達式
● 除了以上兩種以外的表達式就是引用表達式
因爲 Boolean b = (map!=null ? map.get(“Hollis”) : false); 表
達式中,第二位操作數爲 map.get(“test”),雖然 Map 在定義的時候規定了其值
類型爲 Boolean,但是在編譯過程中泛型是會被擦除的(泛型的類型擦除),所以,
其結果就是 Object。那麼根據以上規則判斷,這個表達式就是引用表達式。
又跟據 JLS15.25.3 中規定:
● 如果引用條件表達式出現在賦值上下文或調用上下文中,那麼條件表達式就是
合成表達式
因爲,Boolean b = (map!=null ? map.get(“Hollis”) : false);
其實就是一個賦值上下文(關於賦值上下文相見 JLS 5.2),所以 map!=null ?
map.get(“Hollis”) : false; 就是合成表達式。
那麼 JLS15.25.3 中對合成表達式的操作數類型做了約束:
● 合成的引用條件表達式的類型與其目標類型相同
所以,因爲有了這個約束,編譯器就可以推斷(Java 8 中類型推斷,詳見 JLS
18)出該表達式的第二個操作數和第三個操作數的結果應該都是 Boolean 類型。
所以,在編譯過程中,就可以分別把他們都轉成 Boolean 即可,那麼以上代碼
在 Java 8 中反編譯後內容如下:

Boolean b = maps == null ? Boolean.valueOf(false) : (Boolean)maps.
get("Hollis");

但是在 Java 7 中可沒有這些規定(Java 8 之前的類型推斷功能還很弱),編譯
器只知道表達式的第二位和第三位分別是基本類型和包裝類型,而無法推斷最終表達
式類型。
那麼他就會先根據 JLS 15.25 的規定,把返回值結果轉換成基本類型。然後在
進行變量賦值的時候,再轉換成包裝類型:

 Boolean b = Boolean.valueOf(maps == null ? false : ((Boolean)maps.
get("Hollis")).booleanValue());

所以,相比 Java 8 中多了一步自動拆箱,所以會導致 NPE。

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