JDK自动拆箱下,三目运算符的潜规则
来源:互联网 发布:淘宝网商城首页手机版 编辑:程序博客网 时间:2024/05/16 12:22
最近发现了一个很诡异的NullPointerException,在下面这个方法抛出,一开始怎么都没想明白,dSrc即使为null,那直接赋值给distinct也没问题啊。
- private Doubledistinct;
- private void setParam(Double dSrc, boolean flag) {
- this.distinct = (flag) ? dSrc : 0d;
- }
最后才发现是Java自动拆箱的潜规则,下面我们来看看其所以然。
自动装箱/拆箱
在JDK1.5引入自动装箱/拆箱,提高了我们的开发效率,也让我们的代码变得更加简洁,不用显式转换:
- Double dWrap1 = 10d;
- double d1 = dWrap1;
- double d2 = d1 + dWrap1;
- DoubledWarp2 = d2 + dWrap1;
实际上,自动装箱/拆箱是通过编译器来支持的,JVM并没有改变。我们反编译能看到上面的源码会变成:
- Double dWrap1 = Double.valueOf(10.0d);
- double d1 = dWrap1.doubleValue();
- double d2 = d1 + dWrap1.doubleValue();
- DoubledWarp2 = Double.valueOf(d2 + dWrap1.doubleValue());
编译器的意图很明显,帮我们完成基本类型和封装类型的相互转换;另外,对于封装类的运算中,先要转换成基本类型,再进行计算。
但是,这么自动转换,问题就来了,如果我把dWrap1初始化为null,再赋值给d1,相当于把null赋值给了基本类型double。编译的时候是没有问题的,因为编译器还认为是封装类Double类型,会帮我们自动拆箱赋值给d1,只是运行的时候会抛NullPointerException,如下:
- Double dWarp1 = null;
- double d1 = dWarp1;
这其实是个很低级的bug,很容易防范,加个非空校验就可以避免。一般原则也是,在使用每个Object之前都要做非空校验,一些代码检查工具也会帮我们做这个校验,如FindBugs。所以我们可以写成以下形式:
- Double dWarp1 = null;
- double d1 = 0d;
- if (null != dWarp1) {
- d1 = dWarp1;
- }
三目运算的潜规则
有时候,我们为了代码的简洁性,会引入三目运算符:
- double d1 = (null != dWarp1) ? dWarp1 : 0d;
但是,也有比较诡异的情况:根据条件flag判断,如果true则赋值dWarp1,否则设为默认值0,如下。
- Double dWarp1 = null;
- boolean flag =true;
- DoubledWarp2 = (flag) ? dWarp1 : 0d;
这乍眼一看,很正常嘛,相当于dWarp2 = dWarp1,但是运行起来却会抛异常NullPointerException。
这就是编译器的自动装箱/拆箱转换引起的问题。我们反编译就能看到,原来他对dWarp1做了一层拆箱,这样就出现前面我们所说的问题,如果dWarp1为null的话,就挂了。
- DoubledWarp2 = Double.valueOf((flag) ? dWarp1.doubleValue() : 0.0D);
其实,这是自动装箱/拆箱的特性,只要一个运算中有不同的类型,涉及到类型转换,那么编译器会往下(基本类型)转型,再进行运算。 就是说,如果运算中有int和Integer,Integer会先转成int再计算。
所以下面这种写法照样会抛出NullPointerException:
- Double dWarp1 = null;
- Long l2 = null;
- boolean flag =false;
- DoubledWarp2 = (flag) ? dWarp1 : l2;
因为dWarp1和l2都要先转换成基本类型,而不是互相转换,反编译后变成:
- Double dWarp2 = Double.valueOf((flag)? dWarp1.doubleValue() : l2.longValue());
因此,这里可以改成下面三种方式:
1.在赋值前,先做非空校验,但是这样做比较繁琐,因为很多时候dWarp2是可以接受null的,这个非空判断只是用来避免编译器的自动拆箱异常。
2.避免使用三目运算符,不过估计会有很多人不忍抛弃三目(我也算一个)。
- Double dWarp1 = null;
- boolean flag =true;
- Double dWarp2 = 0d;
- if (flag) {
- dWarp2 = dWarp1;
- }
3.统一运算中的类型,避免类型的混用了。(个人觉得这种比较优雅)
- Double dWarp1 = null;
- boolean flag =true;
- DoubledWarp2 = (flag) ? dWarp1 : Double.valueOf(0);
- JDK自动拆箱下,三目运算符的潜规则
- JDK自动拆箱下,三目运算符的潜规则
- Java自动装/拆箱下,三目运算符的潜规则
- 三目运算符潜规则
- 三目运算符报空指针,JDK自动拆箱
- 三目运算符的一个坑-自动拆箱
- Java三目运算符数值类型的自动转换
- 三目运算符----类型自动提升
- 三目运算符---类型自动提升
- Java进行三目运算时的自动类型转换
- 使用三目运算符:绑定控件内容时的自动判断
- 通过三目运算符来看JAVA的自动拆箱和装箱
- 三目运算符下的空指针异常和自动拆装箱
- java--三目运算符 类型自动提升
- 逻辑运算符、位运算符、移位运算符、三目运算符、运算符的优先级
- C#的空接合运算符 三目运算符
- C语言的三目运算符
- PHP的三目运算符
- 获得ubuntu的版本与codename
- java学习笔记(一)
- 深入理解递归以及汉诺塔问题[数据结构]
- Notepad++配合MinGW直接编译运行C/C++代码的窍门
- tomcat native 安装
- JDK自动拆箱下,三目运算符的潜规则
- 运行时的用法积累
- VC++ 6.0 JNI的实现
- 01. redis 简介&&安装
- android 自定义控件之一
- 怎样使用OpenCV进行人脸识别
- pycharm快捷键、常用设置、包管理
- cell与Model的那些事
- The Java™ Tutorials — Generics :Unbounded Wildcards 非受限通配符