报错信息如下:
2020-06-03 13:02:22.193 [] [DubboServerHandler-120889-thread-8] ERROR com.alibaba.dubbo.rpc.filter.ExceptionFilter 90 -
[DUBBO] Got unchecked and undeclared exception which called by .
service: com.jzd.health.archive.api.si.DiseaseHistoryServiceInterface, method: getRemovedDuplicateFamilyDiseaseNames, exception: java.lang.NullPointerException: null, dubbo version: 2.8.4.1, current host:
java.lang.NullPointerException: null
at com.jzd.health.archive.biz.service.transform.DiseaseHistoryTransform.transform(DiseaseHistoryTransform.java:60)
at com.jzd.health.archive.biz.service.transform.DiseaseHistoryTransform.transform(DiseaseHistoryTransform.java:21)
at com.jzd.frw.common.transformer.AbstractContextualObjectTransformer.transform(AbstractContextualObjectTransformer.java:26)
at com.jzd.frw.common.tr
定位到这里,也就是set这样代码报的NPE。这里的chronicDiseaseFlag是Boolean。
if (CollectionsUtils.isNotEmpty(diseaseInfoDTOS)) {
dto.setChronicDiseaseFlag(diseaseInfoDTOS.get(0) == null ? false : diseaseInfoDTOS.get(0).getChronicDiseaseFlag());
}
这里怎么会有NPE?卡克10分钟…吃完饭才想通很久很久以前,一位同事来问过我类似的问题,也是三目运算符中出现的NPE。直觉告诉我,一定是代码问题,而不是什么“诡异”的问题。
结论:
三目运算符左右表达式在类型对齐时,产生了自动拆箱的操作,导致NPE。也就是说:
diseaseInfoDTOS.get(0)当然不为NULL,但是diseaseInfoDTOS.get(0).getChronicDiseaseFlag()为NULL,因为chronicDiseaseFlag本来就是包装类Boolean,所以赋值为NULL没任何问题。But!在三目运算符中,它自己做了“类型对齐”,这里因为表达式1是false,所以会尝试跟它对齐,把NULL转成基本类型的boolean值,自然就报错喽。
更加详细全面的解释如下文:
答案在这里:
https://mp.weixin.qq.com/s/iQ6qdNv7WTLa3drxNa9png
摘抄下最核心的2句话。
三目运算符condition ?表达式1:表达式2中,高度注意表达式1和2再类型对齐时,可能跑出因自动拆箱导致的NPE异常。
说明:以下两种场景会出发类型对齐的拆箱操作。
1)表达式1或表达式2的值只要有一个是原始类型;(俺的示例就符合这一条)
2)表达式1或表达式2的值的类型不一致,会强制拆箱升级成表示范围更大的那个类型。(俺的示例也符合这一条)
2)能说明三目再背后会把boolean向上转型成Boolean。是的boolean-> Boolean没事;但是null -> Boolean就有事了。
如果不是网上搜搜,自己能通过判断找到为啥这里会出现问题呢?自己不就是死活想不通这里这么会出现NPE,然后就在原地打转?没有进展。可见,排查问题思路不够开阔。
怎么证明三目自动拆箱了呢?反编译。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wwgj4KJ4-1591626663104)(http://note.youdao.com/yws/res/93973/9E4A2AF2766D469BB3B671EC5D2DCFD4)]
发现这样行不通,就用命令行看下
[@ddeMacBook-Air:transform (develop)]$ javac TestTernaryOperatorNPE.java
[@dzjdeMacBook-Air:transform (develop)]$ ls
DiseaseHistoryTransform.java TestTernaryOperatorNPE$DiseaseInfoDTO.class TestTernaryOperatorNPE.java
TestTernaryOperatorNPE$DiseaseHistoryDTO.class TestTernaryOperatorNPE.class
[@dzjdeMacBook-Air:transform (develop)]$ javap -c TestTernaryOperatorNPE.class
Compiled from "TestTernaryOperatorNPE.java"
public class com.dzj.health.archive.biz.service.transform.TestTernaryOperatorNPE {
public .health.archive.biz.service.transform.TestTernaryOperatorNPE();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
public static void main(java.lang.String[]);
Code:
0: new #2 // class com//health/archive/biz/service/transform/TestTernaryOperatorNPE$DiseaseHistoryDTO
3: dup
4: invokespecial #3 // Method com//health/archive/biz/service/transform/TestTernaryOperatorNPE$DiseaseHistoryDTO."":()V
7: astore_1
8: invokestatic #4 // Method java/util/Collections.emptyList:()Ljava/util/List;
11: astore_2
12: aload_2
13: invokeinterface #5, 1 // InterfaceMethod java/util/List.size:()I
18: iconst_1
19: if_icmplt 59
22: aload_1
23: aload_2
24: iconst_0
25: invokeinterface #6, 2 // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
30: ifnonnull 37
33: iconst_0
34: goto 53
37: aload_2
38: iconst_0
39: invokeinterface #6, 2 // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
44: checkcast #7 // class com/dzj/health/archive/biz/service/transform/TestTernaryOperatorNPE$DiseaseInfoDTO
47: invokevirtual #8 // Method com/dzj/health/archive/biz/service/transform/TestTernaryOperatorNPE$DiseaseInfoDTO.getChronicDiseaseFlag:()Ljava/lang/Boolean;
50: invokevirtual #9 // Method java/lang/Boolean.booleanValue:()Z
53: invokestatic #10 // Method java/lang/Boolean.valueOf:(Z)Ljava/lang/Boolean;
56: invokevirtual #11 // Method com/dzj/health/archive/biz/service/transform/TestTernaryOperatorNPE$DiseaseHistoryDTO.setChronicDiseaseFlag:(Ljava/lang/Boolean;)V
59: return
}
也看不出啥名堂。
1) 如果报错,一定是代码问题,不要觉得“诡异”,只是自己受知识面限制,想不到罢了;
2)学会利用编译和反编译查找问题。
最后,啰嗦几句。
曾几何时,特别害怕NPE,因为那时候开发一个功能,总是出现好多好多NPE,修复一个刚部署好,又接着一个,然后又接着一个…
修到让人崩溃。当然,这只能说明什么,那时候真“菜”。
后来不怕NPE了,修NPE很简单,但是找到引发NPE的原因需要花点心思,比如userId在不应该出现NUll的情况下出现了NULL,这个时候不仅只是修复问题,要寻找到根源。
再后来,算这次,遇到过2次三目运算符导致的NPE。怎么都想不通这儿怎么会是NPE!为了避免第三次碰到,这里必须总结下。