淺析Java中三目運算符可能產生的坑


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

  但是,三目運算符也是有一定的語言規范的。在運用不恰當的時候會導致意想不到的問題。

  前段時間遇到(一個由於使用三目運算符導致的問題,其實是因為有三目運算符和自動拆箱同時使用(雖然自動拆箱不是我主動用的)。

一、三目運算符

  三元元素的格式:【條件控制語句】 ? 【表達式1】 : 【表達式2】。

1、對於條件表達式b?x:y,先計算條件b,然后進行判斷。如果b的值為true,計算x的值,運算結果為x的值;否則,計算y的值,運算結果為y的值。

2、一個條件表達式從不會既計算x,又計算y。

3、條件運算符是右結合的,也就是說,從右向左分組計算。例如,a?b:c?d:e將按a?b:(c?d:e)執行。

  優點:一些簡單的邏輯判斷三元運算符可以簡化代碼,去除多余的 if-else 語句。

  缺點:三元運算符使用時必須有返回值,沒有返回值的表達式是不可以使用的。

  使用時一點要注意,考慮好實際情況在進行使用!

二、自動裝箱與自動拆箱

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

  一般我們要創建一個類的對象實例的時候,我們會這樣: Class a = new Class(parameters); 

  當我們創建一個Integer對象時,卻可以這樣: Integer i = 100;(注意:和 int i = 100;是有區別的 ) 實際上,執行上面那句代碼的時候,系統為我們執行了: Integer i = Integer.valueOf(100); 這里暫且不討論這個原理是怎么實現的(何時拆箱、何時裝箱),也略過普通數據類型和對象類型的區別。我們可以理解為,當我們自己寫的代碼符合裝(拆)箱規范的時候,編譯器就會自動幫我們拆(裝)箱。那么,這種不被程序員控制的自動拆(裝)箱會不會存在什么問題呢?

三、問題回顧

  首先,通過你已有的經驗看一下下面這段代碼。如果你得到的結果和后文分析的結果一致(並且你知道原理),那么請忽略本文。如果不一致,請跟我探索下去。

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

  以上這段代碼,是我們在不注意的情況下有可能經常會寫的一類代碼(在很多時候我們都愛使用三目運算符)。當然,這段代碼是存在問題的,執行該代碼,會報NPE。

Exception in thread "main" java.lang.NullPointerException

  首先可以明確的是,既然報了空指針,那么一定是有些地方調用了一個null的對象的某些方法。在這短短的兩行代碼中,看上去只有一處方法調用map.get("test"),但是我們也都是知道,map已經事先初始化過了,不會是Null,那么到底是哪里有空指針呢。

  我們接下來反編譯該代碼。看看我們寫的代碼在經過編譯器處理之后變成了什么樣。反編譯后代碼如下:

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。

  如果代碼這么寫,就不會報錯:

Boolean b = (map!=null ? map.get("test") : Boolean.FALSE);

  就是保證了三目運算符的第二、第三位操作數都為對象類型。這和三目運算符有關。

 五、擴展例子

public class Demo2 { public static void main(String[] args) { // 初始化三個變量
      Integer number1 = 20; Integer number2 = 30; Integer number3 = null; // 表達式
      Integer result = number1 > number2 ? number1 + number2 : number3; System.out.printf("三元表達式的結果為:%d", result); } }

  大家能看出來錯誤嗎?可能不仔細看發現不了問題,下面我把控制台運行結果展示出來。

  看到這個錯誤后我一愣,明明是拿包裝類接收的返回值?不應該報空指針錯誤的啊?然后我將class文件反編譯了一下,此時錯誤一目了然。

  真相大白!當表達式1和表達式2進行類型對齊時拋出了空指針異常。

  那么什么時候會出現類型對齊的情況那?我總結了以下兩種會發出類型對齊的拆箱操作。

1、表達式一和表達式二有一個是基本數據類型。

2、表達式一和表達式二的值類型不一致,會強制拆箱升級成范圍更大的那個表達式的類型。

六、版本差異

  JDK7,報出了空指針異常。

  JDK8,沒有報異常。

  所以還在使用JDK7的小伙伴注意了,為了避免空指針異常,三目運算符中要把基礎類型進行裝箱


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM