使用三目运算符重构业务代码,测试的时候发生了 NPE 的问题。

重构代码非常简单,代码如下:

// 方法返回参数类型为 Integer

// private Integer code;

SimpleObj simpleObj = new SimpleObj();

// 其他业务逻辑

if (simpleObj == null) {

return -1;

} else {

return simpleObj.getCode();

}

这段 if 判断使用三目运算符重构了一把,代码如下:

// 方法返回参数类型为 Integer

SimpleObj simpleObj = new SimpleObj();

// 其他业务逻辑

return simpleObj == null ? -1 : simpleObj.getCode();

测试的时候,第四行代码抛出了空指针,这里代码很简单,显然只有 simpleObj#getCode才有可能发生 NPE 问题。

但是我明明为 simpleObj做过判空判断,simpleObj 对象肯定不是 null,那么只有 simpleObj#getCode 返回为 null。但是我的代码并没有对这个方法返回值做任何操作,为何会触发 NPE?

难道是又是自动拆箱导致的 NPE 问题?

在解答这个问题之前,我们首先复习一下三目运算符。

三目运算符

三目运算符,官方英文名称:Conditional Operator ? :,中文直译条件表达式,本文不纠结名称,统一使用三目运算符。

三目运算符的基本用法非常简单,它由三个操作数的运算符构成,形式为:

?:`

三目运算符的计算从左往右计算,首先需要计算计算表达式 1 ,其结果类型必须为 Boolean 或 boolean,否则发生编译错误。

当表达式 1 的结果为 true,将会执行表达式 2,否则将会执行表达式 3。

表达式 2 与表达式 3 最后的类型必须得有返回结果,即不能为是 void,若为 void ,编译时将会报错。

最后需要注意的是,表达式 2 与表达式 3 不会被同时执行,两者只有一个会被执行。

踩坑案例

了解完三目运算符的基本原理,我们简化一下开头例子,复现一下三目运算符使用过程的一些坑。假设我们的例子简化成如下:

boolean flag = true; //设置成true,保证表达式 2 被执行

int simpleInt = 66;

Integer nullInteger = null;

案例 1

第一个案例我们根据如下计算 result 的值。

int result = flag ? nullInteger : simpleInt;

这个案例为开头的例子的简化版本,运算上述代码,将会发生 NPE 的。

为什么会发发生 NPE 呢?

这里可以给大家一个小技巧,当我们从代码上没办法找到答案时,我们可以试试查看一下编译之后字节码,或许是 Java 编译之后增加某些东西,从而导致问题。

使用 javap -s -c class 查看 class 文件字节码,如下:

可以看到字节码中加入一个拆箱操作,而这个拆箱只有可能发生在 nullInteger。

那么为什么 Java 编译器在编译时会对表达式进行拆箱?难道所有数字类型的包装类型都会进行拆箱吗?

三目运算符表达式发生自动拆箱,其实官方在 「The Java Language Specification(简称:JLS)」15.25 节[1]中做出一些规定,部分内容如下:

JDK7 规范

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.

用大白话讲,如果表达式 2 与表达式 3 类型相同,那么这个不用任何转换,三目运算符表达式结果当然与表达式 2,3 类型一致。

当表达 2 或表达式 3 其中任一一个是基本数据类型,比如 int,而另一个表达式类型为包装类型,比如 Integer,那么三目运算符表达式结果类型将会为基本数据类型,即int。

ps:有没有疑问?为什么不规定最后结果类型都为包装类那?

这是 Java 语言层面一种规范,但是这个规范如果强制让程序员执行,想必平常使用三目运算符将会比较麻烦。所以面对这种情况, Java 在编译器在编译过程加入自动拆箱进制。

所以上述代码可以等同于下述代码:

int result = flag ? nullInteger.intValue() : simpleInt;

如果我们一开始的代码如上所示,那么这里错误点其实就很明显了。

案例 2

接下来我们在第一个案例基础上修改一下:

boolean flag = true; //设置成true,保证表达式 2 被执行

int simpleInt = 66;

Integer nullInteger = null;

Integer objInteger = Integer.valueOf(88);

int result = flag ? nullInteger : objInteger;

运行上述代码,依然会发生 NPE 的问题。当然这次问题发生点与上一个案例不一样,但是错误原因却是一样,还是因为自动拆箱机制导致。

这一次表达式 2 与表达式 3 都为包装类 Integer,所以三目运算符的最后结果类型也会是Integer。

但是由于 result是 int 基本数据类型,好家伙,数据类型不一致,编译器将会对三目运算符的结果进行自动拆箱。由于结果为 null,自动拆箱将报错了。

上述代码等同为:

int result = (flag ? nullInteger : objInteger).intValue();

案例 3

我们再稍微改造一下案例 1 的例子,如下所示:

boolean flag = true; //设置成true,保证表达式 2 被执行

int simpleInt = 66;

Integer nullInteger = null;

Integer result = flag ? nullInteger : simpleInt;

案例 3 与案例 1 右边部分完全相同,只不过左边部分的类型不一样,一个为基本数据类型int,一个为 Integer。

按照案例 1 的分析,这个也会发生 NPE 问题,原因与案例 1 一样。

这个之所以拿出来,其实想说下,上述三目运算符的结果为 int 类型,而左边类型为 Integer,所以这里将会发生自动装箱操作,将 int类型转化为 Integer。

上述代码等同为:

Integer result = Integer.valueOf(flag ? nullInteger.intValue() : simpleInt);

案例 4

最后一个案例,与上面案例都不一样,代码如下:

boolean flag = true; //设置成true,保证表达式 2 被执行

Integer nullInteger = null;

Long objLong = Long.valueOf(88l);

Object result = flag ? nullInteger : objLong;`

运行上述代码,依然将会发生 NPE 的问题。

这个案例表达式 2 与表达式 3 类型不一样,一个为 Integer,一个为 Long,但是这两个类型都是 Number的子类。

面对上述情况,JLS 规定:

Otherwise, binary numeric promotion (§5.6.2[2]) is applied to the operand types, and the type of the conditional expression is the promoted type of the second and third operands.

Note that binary numeric promotion performs value set conversion (§5.1.13[3]) and may perform unboxing conversion (§5.1.8[4]).

大白话讲,当表达式 2 与表达式 3 类型不一致,但是都为数字类型时,低范围类型将会自动转为高范围数据类型,即向上转型。这个过程将会发生自动拆箱。

Java 中向上转型并不需要添加任何转化,但是向下转换必须强制添加类型转换。

上述代码转化比较麻烦,我们先从字节码上来看:

第一步,将 nullInteger拆箱。

第二步,将上一步的值转为 long 类型,即 (long)nullInteger.intValue()。

第三步,由于表达式 2 变成了基本数据类型,表达式 3 为包装类型,根据案例 1 讲到的规则,包装类型需要转为基本数据类型,所以表达式 3 发生了拆箱。

第四步,由于三目运算符最后的结果类型为基本数据类型:long,但是左边类型为 Object,这里就需要把 long 类型装箱转为包装类型。

所以最后代码等同于:

Object result = Long.valueOf(flag ? (long)nullInteger.intValue() : objLong.longValue());

总结

看完上述四个案例,想必大家应该会有种感受,没想到这么简单的三目运算符,既然暗藏这么多「杀机」。

不过大家也不用过度害怕,不使用三目运算符。只要我们在开发过程重点注意包装类型的自动拆箱问题就好了,另外也要注意三目运算符的计算结果再赋值的时候自动拆箱引发的 NPE 的问题。

最好大家在开发过程中,都遵守一定的规范,即保持表达式 2 与表达式 3 的类型一致,不让 Java 编译器有自动拆箱的机会。

建议大家没事经常看下阿里出品的『Java 开发手册』,在最新的「泰山版」就增加三目运算符的这一节规范。

最后一定要做好的单元测试,不要惯性思维,觉得这么简单的一个东西,看起来根本不可能出错的。

三目运算符 java_Java三目运算符中的坑相关推荐

  1. 什么是三目运算符?三目运算符怎么使用?

    1. 什么是三目运算符? 三目运算符又称为"三元运算符"和"条件运算符",在java.C.C++.python.JavaScript.PHP等编程语言中都有三目 ...

  2. python三目运算符_Python 三目运算符

    Python 三目运算符 在javascript中实现三元运算符十分简单,具体代码如下:function getFee(isMember) { return (isMember ? '$2.00' : ...

  3. (四)Asp.net web api中的坑-【api的返回值】

    (四)Asp.net web api中的坑-[api的返回值] 原文:(四)Asp.net web api中的坑-[api的返回值] void无返回值 IHttpActionResult HttpRe ...

  4. JDK中的坑:JDK中这些方法的bug你不要踩

    点击关注公众号,Java干货及时送达 图片来源:白夜追凶 前言: jdk作为我们每天必备的调用类库,里面大量提供了基础类供我们使用.可以说离开jdk,我们的java代码寸步难行,jdk带给我们的便利可 ...

  5. C# System.Timers.Timer中的坑,程序异常退出后timer依然运行问题

    C# System.Timers.Timer中的坑,程序异常退出后timer依然运行问题 参考文章: (1)C# System.Timers.Timer中的坑,程序异常退出后timer依然运行问题 ( ...

  6. 决策树python建模中的坑 :ValueError: Expected 2D array, got 1D array instead:

    决策树python建模中的坑 代码 #coding=utf-8 from sklearn.feature_extraction import DictVectorizerimport csvfrom ...

  7. 小心 Enum Parse 中的坑

    小心 Enum Parse 中的坑 Intro 最近使用枚举的时候,踩了一个小坑,分享一下,主要是枚举从 int 值转成枚举时可能会遇到 Sample 来看下面的示例: 首先定义一个枚举: publi ...

  8. mysql 主从 通俗易懂_MySQL 主从同步架构中你不知道的“坑”(完结篇)

    MySQL 主从同步架构中你不知道的"坑"(完结篇) 收录于话题 #MySQL从入门到放弃 26个 点击上方蓝字,关注我们哟! 前言导读 之前写出一篇文章也是关于这个主从同步架构的 ...

  9. golang中container/list包中的坑

    转载地址:golang中container/list包中的坑 - Go语言中文网 - Golang中文社区 golang中list包用法可以参看golang中container/list包用法_che ...

最新文章

  1. 计算机游戏与动漫设计大赛,我院获第10届中国大学生计算机设计大赛 数字媒体设计类动漫游戏组一等奖...
  2. debian 8 网桥
  3. centos7 go yum 安装_超详细的centos7下载安装Postgresql11(yum安装)教程
  4. exp4me 用java做的实用的csv导出程序 - 名传无线.freeness.yang
  5. 八、Vue cli3详解学习笔记
  6. 一篇文章快速搞懂C++生成随机数
  7. B - Cube HDU - 1220 (数学计数)
  8. DHTML【4】--HTML
  9. 05DotNet基本常用类库
  10. python super()
  11. JavaScript或MyEclipse—如何解决js文件导入到MyEclipse工程后出错?
  12. 使用cisco2811c当pptp ***接入设备
  13. STM32CubeIDE更新ST LINK驱动失败解决方法
  14. 录制Gif动画的软件-ScreenToGif
  15. ubuntu格式化硬盘
  16. 安霸Ambarella CV系列芯片
  17. C/C++ Qt StatusBar 底部状态栏应用
  18. 阿迪达斯携手麦当劳推出篮球明星鞋服;拜耳联合导师计划支持中国医药初创企业 | 美通企业日报...
  19. 前端基础(四)_数据类型的强制转换
  20. 【独家直播】 德哥PG系列课程15讲—PostgreSQL 多场景 沙箱实验从入门到精通

热门文章

  1. Vue报错—Unexpected tab character
  2. vue mint-ui mt-cell-swipe 滑动删除对应的元素
  3. Ubuntu文件权限
  4. struts2设置默认首页
  5. .NET core 发布至IIS中
  6. Python标准库中的re模块
  7. 【第44题】常用的数学工具类1-角度和弧度的转换
  8. Java之数学工具包Math
  9. SpringMVC之拦截器
  10. 杨强教授第四范式内部分享:漫谈《西部世界》、GAN及迁移学习