# 《阿里巴巴JAVA開發手冊-泰山版》提到的三目運算符的空指針問題到底是個怎么回事?
最近,阿里巴巴Java開發手冊發布了最新版,泰山版,這個名字起的不錯,一覽眾山小。
新版據說新增了30+規約,其中有一條規約引起了作者的關注,因為這個問題我很久之前遇到過,曾經在博客中也記錄過。

最初遇到這個問題的是我的同事,他在代碼中使用了三目運算符,代碼在線上運行的時候發生了NPE,經過排查,發現原來是三目運算符和自動拆裝箱之間有一定的關系,導致了空指針。
趁著最新的開發手冊中也提到了這個點,于是把之前的文章內容翻出來并重新整理了一下,帶大家一起回顧下這個知識點。
### 一、三目運算符
對于條件表達式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);
這里暫且不討論這個原理是怎么實現的(何時拆箱、何時裝箱),也略過普通數據類型和對象類型的區別。
我們可以理解為,當我們自己寫的代碼符合裝(拆)箱規范的時候,編譯器就會自動幫我們拆(裝)箱。
那么,這種不被程序員控制的自動拆(裝)箱會不會存在什么問題呢?
### 三、問題回顧
首先,通過你已有的經驗看一下下面這段代碼:
~~~
1Map<String,Boolean> map = new HashMap<String, Boolean>();
2Boolean b = (map!=null ? map.get("test") : false);
~~~
以上這段代碼,是我們在不注意的情況下有可能經常會寫的一類代碼(在很多時候我們都愛使用三目運算符)。當然,這段代碼是存在問題的,執行該代碼,會報NPE.
`Exception in thread "main" java.lang.NullPointerException`
首先可以明確的是,既然報了空指針,那么一定是有些地方調用了一個null的對象的某些方法。
在這短短的兩行代碼中,看上去只有一處方法調用`map.get("test")`,但是我們也都是知道,map已經事先初始化過了,不會是Null,那么到底是哪里有空指針呢。
我們接下來[反編譯](https://link.zhihu.com/?target=http%3A//www.hollischuang.com/archives/58)一下該代碼。看看我們寫的代碼在經過編譯器處理之后變成了什么樣。
反編譯后代碼如下:
~~~
1HashMap hashmap = new HashMap();
2Boolean 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的原因應該是三目運算符和自動拆箱導致了空指針異常。
根據規定,三目運算符的第二、第三位操作數的返回值類型應該是一樣的,這樣才能當把一個三目運算符的結果賦值給一個變量。
如:`Person i = a>b : i1:i2;`?,就要求i1和i2的類型都必須是Person才行。
因為Java中存在一種特殊的情況,那就是基本數據類型和包裝數據類型可以通過自動拆裝箱的方式互相轉換。即可以定義int i = new Integer(10);也可以定義Integer i= 10;
那如果,三目運算符的第二位和第三位的操作數的類型分別是基本數據類型和包裝類型對象時,就需要有一方需要進行自動拆裝箱。
那到底如何做的呢,根據三目運算符的語法規范。參見[jls-15.25](https://link.zhihu.com/?target=http%3A//docs.oracle.com/javase/specs/jls/se7/html/jls-15.html%23jls-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。
### 五、問題解決
如果代碼這么寫,就不會報錯:
~~~
1Map<String,Boolean> map = new HashMap<String, Boolean>();
2Boolean b = (map!=null ? map.get("test") : Boolean.FALSE);
~~~
就是保證了三目運算符的第二第三位操作數都為對象類型。
這和三目運算符有關。
- java開發手冊
- 嵩山版手冊
- 為什么禁止使用 Apache Beanutils 進行屬性的 copy ?
- 為什么阿里巴巴要求日期格式化時必須有使用y表示年,而不能用Y?
- 《Java 開發手冊 - 泰山版》提到的三目運算符 的空指針問題到底是個怎么回事?
- 為什么建議初始化 HashMap 的容量大小?
- Java 開發手冊建議創建 HashMap 時設置初始 化容量,但是多少合適呢?
- 為什么禁止使用 Executors 創建線程池?
- 為什么要求謹慎使用 ArrayList 中的 subList 方法?
- 為什么不建議在 for 循環中使用“+”進行字符 串拼接?
- 為什么禁止在 foreach 循環里進行元素的 remove/add 操作?
- 為什么禁止工程師直接使用日志系統 (Log4j、 Logback) 中的 API ?
- 為什么禁止把 SimpleDateFormat 定義成 static 變量?
- 為什么禁止開發人員使用 isSuccess 作為變量名?
- 為什么禁止開發人員修改 serialVersionUID 字段的值?
- 為什么建議開發者謹慎使用繼承?
- 為什么禁止使用 count( 列名 ) 或 count( 常量 ) 來替代 count(*) ?