最近,小黑哥在一个业务改造中,使用三目运算符重构了业务代码,没想到测试的时候竟然发生 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>?<表达式 2>:<表达式 3>

条件表达式的计算从左往右计算,首先需要计算计算表达式 1 ,其结果类型必须为 Booleanboolean,否则发生编译错误。

当表达式 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 节中做出一些规定,部分内容如下:

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) 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) and may perform unboxing conversion (§5.1.8).

大白话讲,当表达式 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 开发手册』,在最新的「泰山版」就增加条件表达式的这一节规范。

ps:公号消息回复:『开发手册』,获取最新版的 Java 开发手册。

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

参考

  1. Java 开发手册-泰山版
  2. 《Java 开发手册》解读:三目运算符为何会导致 NPE?

我去,这么简单的条件表达式竟然也有这么多坑相关推荐

  1. 实际工作中遇到的技术难题与大家交流(工作流条件表达式计算部分),希望技术高手能给于指点

    有一个审核的工作流程,默认情况下是 [杭州编辑审核]--> [北京编辑审核]--> [信息发布员审核]--> [信息发布] 这个是一个典型的工作审核流程,我们可以简单的建立3个角色, ...

  2. 金仓数据库 KingbaseES SQL 语言参考手册 (7. 条件表达式)

    7. 条件表达式 条件表达式 指定一个或多个表达式和逻辑(布尔)运算符的组合,并返回TRUE.FALSE的值或UNKNOWN. 本章包含以下部分: SQL条件简介 比较条件 浮点条件 逻辑条件 模式匹 ...

  3. Python 之条件表达式

    目录 示例 1: 示例 2: 条件表达式非 Python 所独有,在其他编程语言中也称之为三元运算符,三目运算符,是基于真(true)或假(false)的条件进行判断的表达式.其通用语法为: expr ...

  4. 【C语言探索之旅】 第一部分第六课:条件表达式

    内容简介 1.课程大纲 2.第一部分第六课:条件表达式 3.第一部分第七课预告:循环语句 课程大纲 我们的课程分为四大部分,每一个部分结束后都会有练习题,并会公布答案.还会带大家用C语言编写三个游戏. ...

  5. python 条件表达式换行_Python基础语法 - LongKing-Xu的个人空间 - OSCHINA - 中文开源技术交流社区...

    python基础语法 一.标识符 在Python中,所有标识符可以包括英文.数字以及下划线(_),但不能以数字开头. 在Python中的标识符是区分大小写的. 在Python中以下划线开头的标识符是有 ...

  6. 代码重构(四):条件表达式重构规则

    继续更新有关重构的博客,前三篇是关于类.函数和数据的重构的博客,内容还算比较 充实吧.今天继续更新,本篇博客的主题是关于条件表达式的重构规则.有时候在实现比较复杂的业务逻辑时,各种条件各种嵌套.如果处 ...

  7. Kotlin学习(二)—— 基本语法,函数,变量,字符串模板,条件表达式,null,类型检测,for,while,when,区间,集合

    一.基本语法 Kotlin的很多概念跟JAVA是有类似的,所以我应该不会像我的JAVA之旅一样那么的详细,但是不用担心,你会看的很明白的,我也是根据官方的文档来学习的 我们在IDEA中创建一个项目Ko ...

  8. Oracle数据库:条件表达式case when then else end,decode函数,oracle单行函数练习示例

    Oracle数据库:条件表达式case when then else end,decode函数,oracle单行函数练习示例 2022找工作是学历.能力和运气的超强结合体,遇到寒冬,大厂不招人,可能很 ...

  9. C语言if条件表达式只能是,小白基础知识必备|| 条件表达式

    原标题:小白基础知识必备|| 条件表达式 if-else 条件表达式 简单来说,条件表达式使我们可以测试变量. 打个比方,我们可以这样说: "假如变量的值等于 7,就执行这样那样的操作.&q ...

  10. 改善代码设计 —— 简化条件表达式(Simplifying Conditional Expressions)

    系列博客 1. 改善代码设计 -- 优化函数的构成(Composing Methods) 2. 改善代码设计 -- 优化物件之间的特性(Moving Features Between Objects) ...

最新文章

  1. Nat. Biotech. | AI、药物重定位和同行评审
  2. 从王者荣耀AI看人工智能与游戏结合的未来意义
  3. springboot . 配置jpa使用
  4. Java读取xml文件的四种方法
  5. SPOJ - DISUBSTR Distinct Substrings(后缀数组)
  6. 联想EDU同传系统 版本7.5 7.6在机房中出现的一些故障和解决办法
  7. Angular Component template函数执行上下文的对象
  8. android eclipse 配置 在项目右击选择properties
  9. 手工sql注入常规总结
  10. 分布式锁的几种实现方式(转)
  11. 如何获得完美的调色板?完美的配色素材专辑拿走!
  12. 课节5:图神经网络算法(二):GraphSage实践
  13. SAP License:ERP系统会计凭证中的那些必填项
  14. 肤色检测一例-使用rgb颜色模型
  15. numpy—np.logspace
  16. 宝塔linux面板mysql数据库误删恢复过程
  17. 个人记录——洛谷试炼场,BOSS战!【新手村】
  18. 如何让素数分列C语言,哥德巴赫猜想栾生三生素数无限波林那克猜想两素数差证明,(定稿...
  19. 计算机u盘病毒清除方式,清除文件夹exe病毒方法
  20. 全文索引elasticsearch

热门文章

  1. AO如何获取SDE数据库中的数据
  2. C#中奇怪的Queue T 行为!
  3. 大话西游之仙侣履奇缘——[超经典台词]
  4. python中循环结构break_Python编程10:跳出循环结构之break和continue
  5. php读取excel公式取值,通用PHPExcel导出函数代码
  6. 练习4.1 根据后序和中序遍历输出先序遍历 (25 分)
  7. php pacs,PACS系统
  8. word图表自动编号
  9. 深度学习(四十二)网络压缩-未完待续
  10. 写入接口c语言_嵌入式LCD的接口类型详解