• JDK自己主动拆箱下,三目运算符的潜规则


             近期发现了一个非常诡异的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;

             由于dWarp1l2都要先转换成基本类型,而不是互相转换,反编译后变成:

        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);

  • 相关阅读:
    (一)Kubernetes 系统基础
    Linux-网络RAID技术DRBD
    Linux-存储服务之NFS
    ELK快速入门(五)配置nginx代理kibana
    ELK快速入门(四)filebeat替代logstash收集日志
    ELK快速入门(三)logstash收集日志写入redis
    渗透测试工具集合
    CVE-2019-0708(非蓝屏poc)远程桌面代码执行漏洞复现
    代码审计-thinkphp3.2.3框架漏洞sql注入
    渗透测试-端口复用正向后门
  • 原文地址:https://www.cnblogs.com/zfyouxi/p/4184071.html
Copyright © 2020-2023  润新知