近期发现了一个非常诡异的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,非常easy防范,加个非空校验就能够避免。一般原则也是,在使用每一个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自己主动拆箱下,三目运算符的潜规则的更多相关文章

  1. Java的自动装箱/拆箱

    概述 自JDK1.5开始, 引入了自动装箱/拆箱这一语法糖, 它使程序员的代码变得更加简洁, 不再需要进行显式转换.基本类型与包装类型在某些操作符的作用下, 包装类型调用valueOf()方法将原始类 ...

  2. 深入解析Java中的装箱和拆箱

    自己主动装箱和拆箱问题是Java中一个老生常谈的问题了,今天我们就来一些看一下装箱和拆箱中的若干问题.本文先讲述装箱和拆箱最主要的东西,再来看一以下试笔试中常常遇到的与装箱.拆箱相关的问题. 下面是本 ...

  3. [Java开发之路](23)装箱与拆箱

    1. 简单介绍 大家对基本数据类型都很熟悉.比如 int.float.double.boolean.char 等.基本数据类型是不具备对象的特性,比方基本类型不能调用方法.功能简单. ..,为了让基本 ...

  4. Java自动装箱和自动拆箱操作

    1.Java数据类型 在介绍Java的自动装箱和拆箱之前,我们先来了解一下Java的基本数据类型. 在Java中,数据类型可以分为两大种,Primitive Type(基本类型)和Reference ...

  5. [转]JAVA自动装箱和拆箱

    http://www.cnblogs.com/dolphin0520/p/3780005.html 1.Java数据类型 装箱和拆箱之前,我们先来了解一下Java的基本数据类型. 在Java中,数据类 ...

  6. JAVA进阶之旅(一)——增强for循环,基本数据类型的自动拆箱与装箱,享元设计模式,枚举的概述,枚举的应用,枚举的构造方法,枚举的抽象方法

    JAVA进阶之旅(一)--增强for循环,基本数据类型的自动拆箱与装箱,享元设计模式,枚举的概述,枚举的应用,枚举的构造方法,枚举的抽象方法 学完我们的java之旅,其实收获还是很多的,但是依然还有很 ...

  7. java基础(七)-----深入剖析Java中的装箱和拆箱

    本文主要介绍Java中的自动拆箱与自动装箱的有关知识. 基本数据类型 基本类型,或者叫做内置类型,是Java中不同于类(Class)的特殊类型.它们是我们编程中使用最频繁的类型. Java是一种强类型 ...

  8. Java包装类及其拆箱装箱

    Java包装类,Wrapper~由于在java中,数据类型总共可分为两大种,基本数据类型(值类型)和类类型(引用数据类型).基本类型的数据不是对象,所以对于要将数据类型作为对象来使用的情况,java提 ...

  9. JAVA中拆箱和装箱

    浅谈JAVA中拆箱与装箱 一.  什么是装箱?什么是拆箱? 在Java SE5之前,如果要生成一个数值为10的Integer对象,必须这样进行: Integer i = new Integer(10) ...

随机推荐

  1. Qt学习 之 数据库(支持10种数据库)

    Qt 提供了 QtSql 模块来提供平台独立的基于 SQL 的数据库操作.这里我们所说的“平台独立”,既包括操作系统平台,有包括各个数据库平台.另外,我们强调了“基于 SQL”,因为 NoSQL 数据 ...

  2. MySQL PrepareStatement基本的两种模式&客户端空间占用的源码分析

    关于预编译(PrepareStatement),对于所有的JDBC驱动程序来讲,有一个共同的功能,就是“防止SQL注入”,类似Oracle还有一种“软解析”的概念,它非常适合应用于OLTP类型的系统中 ...

  3. [Android学习笔记5]四大应用组件之一:Service 下

    绑定方式的Service使用 在实现绑定服务时,最重要的是定义onBind()回调方法返回的接口,有三种方式: 1. 继承Binder类 2. 使用Messenger 3. 使用AIDL 这里对1,2 ...

  4. HDU 4720 Naive and Silly Muggles (外切圆心)

    Naive and Silly Muggles Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Oth ...

  5. IOS-图片操作集合

    编辑图片的几个方法 第一种 先用UIImage对象加载一张图片 然后转化成CGImageRef放到CGContext中去编辑 第二种  用CGImageCreate函数创建CGImageRef 然后把 ...

  6. iOS中的图像处理(三)——混合运算

    有时候,单独对一张图像进行处理是很难或者根本达不到我们想要的效果的.一个好的滤镜效果的诞生,往往要经过很多复杂步骤.细致微调.图片应用效果观察以及很多图层叠加. 我在JSWidget上发现了一些常用混 ...

  7. iOS开发中两个不错的宏定义

    /** Synthsize a weak or strong reference. Example: @weakify(self) [self doSomething^{ @strongify(sel ...

  8. ios 调用webservice整理

    资料地址:http://blog.sina.com.cn/s/blog_a30ee5f701016yn3.html 学iphone开发有一段时间了,对于我这个新手来说,学习过程中,遇到不少问题,尤其是 ...

  9. FileDescriptor

    FileDescriptor 在java中的java.io包下面 public final class FileDescriptor { ... } 官方的解释: 文件描述符类的实例用作与基础机器有关 ...

  10. ZOJ 3490 String Successor 字符串处理

    一道模拟题,来模拟进位 暴力的从右往左扫描,按规则求后继就好了.除了Sample已给出的,还有一些需要注意的地方: 9的后继是10,而不是00: (z)的后继是(aa),而不是a(a): 输入虽然最长 ...