三目运算符引起的NPE

文章目录

  • 一、诡异的NPE
  • 二、原因:三目运算符表达式类型对齐拆箱导致NPE
  • 三、继续深究
  • 四、总结

情况这段代码:

一、诡异的NPE

报错信息如下:

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

这里怎么会有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中,高度注意表达式12再类型对齐时,可能跑出因自动拆箱导致的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!为了避免第三次碰到,这里必须总结下。

你可能感兴趣的:(Java)