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

简介: 最近在一个业务改造中,使用三目运算符重构了业务代码,没想到测试的时候竟然发生 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();
相关文章
|
8月前
|
存储 算法 编译器
【c 语言 】移位操作符详解
【c 语言 】移位操作符详解
383 0
|
5月前
正则表达式的限定符、或运算符、字符类、元字符、贪婪/懒惰匹配
本文介绍了正则表达式中的限定符、或运算符、字符类、元字符以及贪婪与懒惰匹配的概念和用法。
47 5
|
7月前
|
编译器 C语言
【C语言】:中移位操作符,位操作符详运算规则详解
【C语言】:中移位操作符,位操作符详运算规则详解
59 1
|
7月前
|
编译器 C语言
C语言学习记录——操作符详解知识点选记(算术操作符、单目操作符、移位操作符、关系操作符、逻辑操作符、条件操作符......)二
C语言学习记录——操作符详解知识点选记(算术操作符、单目操作符、移位操作符、关系操作符、逻辑操作符、条件操作符......)二
59 3
|
7月前
|
存储 编译器 C语言
C语言学习记录——操作符详解知识点选记(算术操作符、单目操作符、移位操作符、关系操作符、逻辑操作符、条件操作符......)一
C语言学习记录——操作符详解知识点选记(算术操作符、单目操作符、移位操作符、关系操作符、逻辑操作符、条件操作符......)一
48 1
|
8月前
|
Python Windows
python语法中错误的运算符或分隔符
【5月更文挑战第19天】python语法中错误的运算符或分隔符
78 1
|
机器学习/深度学习 存储 Linux
Linux基本操作符(2)
Linux基本操作符(2)
70 0
|
8月前
|
C语言
【C语言】位操作符与移位操作符练习
【C语言】位操作符与移位操作符练习
【C语言】位操作符与移位操作符练习
|
存储 编译器 C语言
【C语言初阶】带你轻松玩转所有常用操作符(1) ——算数操作符,移位操作符,位操作符
【C语言初阶】带你轻松玩转所有常用操作符(1) ——算数操作符,移位操作符,位操作符
98 0
|
程序员
三目运算符的使用
三目运算符的使用
126 1

热门文章

最新文章