三目运算符报空指针,JDK自动拆箱

版权声明:本文为博主diweikang原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:[https://blog.csdn.net/diweikang/article/details/51884960]
(https://blog.csdn.net/diweikang/article/details/51884960)

程序中的问题

最近发现了一个很诡异的NullPointerException,在下面这个方法抛出,一开始怎么都没想明白,dClass.getD()即使为null,那直接赋值给d也没问题啊。

class DClass {
 
	private Double d;
 
	public Double getD() {
		return d;
	}
 
	public void setD(Double d) {
		this.d = d;
	}
}
 
public class SanMuYunSuanFuTest {
 
	@Test
	public void getParam() {
		Map<String, Object> map = new HashMap<String, Object>();
		DClass dClass = new DClass();
		Double d = map.get("d") == null ? dClass.getD() : 3.0;
		System.out.println(d);
	}
}

后来更改三目运算符的那一行

Double d = map.get("d") == null ? null : 3.0;

结果就不报错了。和同事研究之后才发现这是由Java自动拆箱导致的

自动装箱/拆箱

在JDK1.5引入自动装箱/拆箱,提高了我们的开发效率,也让我们的代码变得更加简洁,不用显式转换:

	Double dWrap1 = 10d;  
	double d1 = dWrap1;; 
	double d2 = d1 + dWrap1;
	Double dWarp2 = d2 + dWrap1;

实际上,自动装箱/拆箱是通过编译器来支持的,JVM并没有改变。我们反编译能看到上面的源码会变成:


	Double dWrap1 = Double.valueOf(10.0d);  
	double d1 = dWrap1.doubleValue();  
	double d2 = d1 + dWrap1.doubleValue();  
	Double dWarp2 = Double.valueOf(d2 + dWrap1.doubleValue()); 

编译器的意图很明显,帮我们完成基本类型和封装类型的相互转换;另外,对于封装类的运算中,先要转换成基本类型,再进行计算。
但是,这么自动转换,问题就来了,如果我把dWrap1初始化为null,再赋值给d1,相当于把null赋值给了基本类型double。编译的时候是没有问题的,因为编译器还认为是封装类Double类型,会帮我们自动拆箱赋值给d1,只是运行的时候会抛NullPointerException,如下:

    Double dWarp1 = null;  
    double d1 = dWarp1; 

这其实是个很低级的bug,很容易防范,加个非空校验就可以避免。一般原则也是,在使用每个Object之前都要做非空校验,所以我们可以写成以下形式:

	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;  
	Double dWarp2 = (flag) ? dWarp1 : 0.0d;

这乍眼一看,很正常嘛,相当于dWarp2 = dWarp1,但是运行起来却会抛异常NullPointerException
这就是编译器的自动装箱/拆箱转换引起的问题。我们反编译就能看到,原来他对dWarp1做了一层拆箱,这样就出现前面我们所说的问题,如果dWarp1为null的话,就挂了。

	Double dWarp2 = Double.valueOf((flag) ? dWarp1.doubleValue() : 0.0D);

其实,这是自动装箱/拆箱的特性,只要一个运算中有不同的类型,涉及到类型转换,那么编译器会往下(基本类型)转型,再进行运算。 就是说,如果运算中有int和Integer,Integer会先转成int再计算。
所以下面这种写法照样会抛出NullPointerException:

	Double dWarp1 = null;  
	Long l2 = null;  
	boolean flag =false;  
	Double dWarp2 = (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;  
	Double dWarp2 = (flag) ? dWarp1 : Double.valueOf(0); 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值