这么简单的三目运算符,竟然这么多坑?(一)

简介: 最近在一个业务改造中,使用三目运算符重构了业务代码,没想到测试的时候竟然发生 NPE 的问题。

最近在一个业务改造中,使用三目运算符重构了业务代码,没想到测试的时候竟然发生 NPE 的问题。

77.jpg

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

// 方法返回参数类型为 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 文件字节码,如下:

78.jpg

可以看到字节码中加入一个拆箱操作,而这个拆箱只有可能发生在 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();
相关文章
|
6月前
|
编译器 C语言
C语言的转义字符,转义字符的用法
C语言的转义字符,转义字符的用法
|
2月前
|
JavaScript 前端开发
在一般编写代码时可以不写分号 ; 但是遇到中括号 小括号 模板字符串的时候必须在前面加一个分号,否则会报错
JavaScript中通常可以省略分号,但在某些语法结构前必须加分号,如中括号、小括号或模板字符串前,以避免语法错误。
35 1
|
6月前
|
Python
python语法中缺少括号
【5月更文挑战第19天】
50 2
|
6月前
|
C语言 C++
每天一道C语言编程:(去掉:双斜杠注释,去掉空格)
每天一道C语言编程:(去掉:双斜杠注释,去掉空格)
37 0
正则中的[^]与通配符里面的[!]的区别
正则中的[^]与通配符里面的[!]的区别
|
6月前
正则字符集操作符
正则字符集操作符
31 1
|
关系型数据库 MySQL
mysql使用where条件语句中文引号转义
mysql使用where条件语句中文引号转义
93 0
|
6月前
|
IDE 开发工具 C++
C++变量命名规则
C++变量命名规则
81 0
|
6月前
正则表达式语法讲解
正则表达式语法讲解
57 0