Java三元表达式装箱拆箱NPE问题
作者:互联网
问题
今天在测试环境的运营后台查询商品库存时发现后端接口报错,返回code为904,该错误码表示内部错误。于是在微服务日志里查看,发现某方法报了NPE(java.lang.NullPointer)。
方法里关键的报错代码如下:
public Integer queryXxx(String xx, String yy) {
...
XxxRo xxxRo = queryXxxRo(xx, yy);
return xxxRo != null ? xxxRo.getQuantity() : 0;
}
日志里错误异常堆栈里看到抛NPE异常的行号对应这一行代码:
return xxxRo != null ? xxxRo.getQuantity() : 0;
初探
初一看如果变量xxxRo
为null,那么xxxRo.getQuantity()
会抛NPE。
可语句里判断了的xxxRo
不为null才执行,否则返回0,按理说变量xxxRo
为null应返回0。
queryXxxRo(xx, yy)
是从Redis里查询数据,将相关参数拼好key在Redis去查发现有数据。
Redis里存储类型为hash,对应XxxRo里的每个字段,其中hget xxx quantity
值为4000000012。
复现
本地启动库存服务,通过dubbo支持的telnet里invoke
命令调用该接口,也是那一行代码抛NPE。
我在return xxxRo != null ? xxxRo.getQuantity() : 0;
这一行代码打了个断点调试,
发现xxxRo
不为null,在IDEA里展开该对象,其中各字段都有值,只有quantity
字段为null。
通过Fn + Option + F8调出Evalute窗口,将xxxRo != null ? xxxRo.getQuantity() : 0
复制进去执行,结果为null,并没有抛NPE。
F9放开断点进行执行,日志里打印NPE,跟测试环境一致。
分析
仔细审视这行代码,它用到了三元表达式来判断,表达式执行后直接return,而方法的返回是Integer类型,
而xxxRo.getQuantity()
也是Integer类型,好像没问题。
注意到三元表达式的另一个分支返回的是0,想起Java在装箱/拆箱时(boxing/unboxing)可能会有NPE,
刚才本地复现时通过断点在IDEA的看到xxxRo的quantity
字段为null,因为返回值是0,
可能这里先进行了拆箱,然后进行装箱的转换。
模拟
int b = (Integer) null;
这行代码null经过装箱,然后自动拆箱时抛了NPE
继续通过几个例子来模拟:
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Optional;
/**
* @author cdfive
*/
public class SpecialNPETest {
public static void main(String[] args) {
// Basic test
basic();
// OK
case1();
// NPE
case2();
// NPE
case3();
// OK
case4();
// OK
case5();
// OK
case6();
}
private static void basic() {
System.out.println("basic start");
// OK
Integer a = (Integer) null;
// NPE
try {
int b = (Integer) null;
} catch (Exception e) {
System.out.println("exception=" + e.getClass().getName() + ",msg=" + e.getMessage());
}
System.out.println();
System.out.println("basic end");
}
private static void case1() {
System.out.println("case1 start");
try {
Item item = new Item();
item.setId(1);
item.setQuantity(5);
// OK
Integer result = item != null ? item.getQuantity() : 0;
System.out.println(result);
} catch (Exception e) {
System.out.println("case1 error,exception=" + e.getClass().getName() + ",msg=" + e.getMessage());
}
System.out.println("case1 end");
System.out.println();
}
private static void case2() {
System.out.println("case2 start");
try {
Item item = new Item();
item.setId(1);
// NPE
Integer result = item != null ? item.getQuantity() : 0;
} catch (Exception e) {
System.out.println("case2 error,exception=" + e.getClass().getName() + ",msg=" + e.getMessage());
}
System.out.println("case2 end");
System.out.println();
}
private static void case3() {
System.out.println("case3 start");
try {
Item item = new Item();
item.setId(1);
// NPE
int result = item != null ? item.getQuantity() : 0;
System.out.println(result);
} catch (Exception e) {
System.out.println("case3 error,exception=" + e.getClass().getName() + ",msg=" + e.getMessage());
}
System.out.println("case3 end");
System.out.println();
}
private static void case4() {
System.out.println("case4 start");
try {
Item item = new Item();
item.setId(1);
// OK
Integer result;
if (item != null) {
result = item.getQuantity();
} else {
result = 0;
}
System.out.println(result);
} catch (Exception e) {
System.out.println("case4 error,exception=" + e.getClass().getName() + ",msg=" + e.getMessage());
}
System.out.println("case4 end");
System.out.println();
}
private static void case5() {
System.out.println("case5 start");
try {
Item item = new Item();
item.setId(1);
// OK
Integer result = item != null ? item.getQuantity() : Integer.valueOf(0);
System.out.println(result);
} catch (Exception e) {
System.out.println("case5 error,exception=" + e.getClass().getName() + ",msg=" + e.getMessage());
}
System.out.println("case5 end");
System.out.println();
}
private static void case6() {
System.out.println("case6 start");
try {
Item item = new Item();
item.setId(1);
// OK
Integer result = Optional.ofNullable(item).map(o -> o.getQuantity()).orElse(null);
System.out.println(result);
} catch (Exception e) {
System.out.println("case6 error,exception=" + e.getClass().getName() + ",msg=" + e.getMessage());
}
System.out.println("case6 end");
System.out.println();
}
@NoArgsConstructor
@AllArgsConstructor
@Data
private static class Item implements Serializable {
private Integer id;
private Integer quantity;
}
}
解决
return xxxRo != null ? xxxRo.getQuantity() : 0;
修改这行代码。
3种思路:
- 改用if/else判断
Integer result;
if (xxxRo != null) {
result = xxxRo.getQuantity();
} else {
result = 0;
}
- 基础类型0改为包装类型Integer.value(0)
Integer result = xxxRo != null ? xxxRo.getQuantity() : Integer.valueOf(0);
- 改用Optional处理
Integer result = Optional.ofNullable(xxxRo).map(o -> o.getQuantity()).orElse(0);
注意:
- 当xxxRo不为null,xxxRo里的quantity为null时,前2种方式返回的是null,第3种方式里返回的是0
- 当xxxRo为null时,3中方式都返回0
思考
刚才有个点忽略了,通过hget xxx quantity
在Redis查出来值为4000000012,为何xxxRo里的quantity字段为null?
注意到日志里还有一个异常:
java.lang.NumberFormatException: For input string: "4000000012"
这里因为4000000012超过了Integer.MAX
的值2147483647,项目框架里Redis的hash转换为Ro对象时用的Integer.valueOf()
,
该方法抛的NumberFormatException
,转换单个字段失败后记录了错误日志并继续执行。
4000000012是其它系统推过来的值,经检查日志和沟通,是测试同学另外一个系统的界面上设置的值过大。
标签:拆箱,Java,NPE,System,item,xxxRo,println,null,out 来源: https://www.cnblogs.com/cdfive2018/p/15434148.html