参考
问题
下面这段代码竟然报了NullPointerException
异常.
public static void main(String[] args) { Map<String, Boolean> map = new HashMap<>(); // Exception in thread "main" java.lang.NullPointerException boolean b = (map!=null ? map.get("test") : false); }
问题来源是Java的Autoboxing和Boxing机制.
三目运算符求值顺序
首先是右结合的.
The conditional operator is syntactically right-associative (it groups right-to-left). Thus, a?b:c?d:e?f:g means the same as a?b:(c?d:(e?f:g)).
其次再关注整体的返回值类型是什么, 首先 ? Expression1 : Expression2
中的两个表达式, 要么是同一个类型, 要么是能够相互转换的, 但是最后会统一为一个类型, 规则(下面只摘录了一部分, 完整的可以去三目运算符的官方文档查看)如下:
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.
对于上面的boolean b = (map!=null ? map.get("test") : false);
:
- map.get("test") 的返回值是Boolean
- false 是原始类型boolean
根据规则2
, boolean
是原始类型, 而Boolean
类型是boolean
装箱后的结果, 即Boolean
. 所以最终的返回值类型为boolean
.
然后开始对整体求值, 首先map != null
肯定是成立的, 所以会对map.get("test")
求值, 因为map中不存在test
这个key, 所以get方法返回了null
, 这也是整体的返回值. 因为map.get()
返回值类型是Boolean
, 而因为规则2
, 所以会将map.get()
对的返回值进行拆箱操作, 即调用Boolean.booleanValue()
方法, 而此时map.get()
的返回值是一个null
, 所以出现了NullPointerException
.
经过进一步测试, 有以下结果:
根据B
那句赋值语句来看, 应该是编译器判断又要从boolean
转换回Boolean
所以map.get()
的结果应该不会再拆箱了.
Map<String, Boolean> map = new HashMap<>();
// Exception in thread "main" java.lang.NullPointerException
// boolean b = (map!=null ? map.get("test") : false);
// 没有异常
// Boolean B = (map!=null ? map.get("test") : false);
// Exception in thread "main" java.lang.NullPointerException
// if ((map!=null ? map.get("test") : false)){
// ;
// }
// Exception in thread "main" java.lang.NullPointerException
// boolean b1 = (map!=null ? map.get("test") : Boolean.FALSE);