当前位置: 首页 > 科技观察

Java自动拆箱空指针异常,消防员在线

时间:2023-03-23 11:30:19 科技观察

转载请联系程序新视界公众号。公司搬迁,临时做装修工,提前两个小时到公司拆设备。因此,客户反映某些功能偶尔无法使用。于是就参与了救火,和写这段代码的小伙伴一起查了原因。最后发现,业务偶尔不能使用,是因为Long类型自动拆箱,导致空指针异常。下面我就带大家分析一下Java中基本类型的封装类在拆箱打包过程中做了什么,为什么会出现空指针异常,以及面试中会出现的相关面试题。问题重现以下是重现异常场景的一个简单例子。publicclassBoxTest{publicstaticvoidmain(String[]args){Mapresult=httpRequest();longuserId=(Long)result.get("userId");}//模拟一个HTTP请求privatestaticMaphttpRequest(){Mapmap=newHashMap<>();map.put("userId",null);returnmap;}}基本场景是请求一个接口,从接口中获取某个值。为Long类型,从Map获取值后,强制转换为Long类型。当接口返回的userId为null时,强制执行此块时会抛出空指针异常:Exceptioninthread"main"java.lang.NullPointerExceptionatcom.choupangxia.box.BoxTest.main(BoxTest.java:15)以上场景和下面的代码一样,异常效果:publicclassBoxTest{publicstaticlonggetValue(longvalue){returnvalue;}publicstaticvoidmain(String[]args){Longvalue=null;getValue(value);}}上面的代码也是异常由拆箱Long类型引起的,但代码中有一个,参数中有一个。为了简化分析,我们以第二种为例进行说明。原因分析一开始你可能会疑惑,抛出异常的代码并没有调用对象的方法,怎么会有空指针呢?主要涉及的是自动拆箱操作。是拆箱造成的吗?让我们通过字节码看一下。使用javap-c查看对应的字节码:publicclasscom.choupangxia.box.BoxTest{publiccom.choupangxia.box.BoxTest();Code:0:aload_01:invokespecial#1//Methodjava/lang/Object."":()V4:returnpublicstaticlonggetValue(long);代码:0:lload_01:lreturnpublicstaticvoidmain(java.lang.String[]);代码:0:aconst_null1:astore_12:aload_13:invokevirtual#2//Methodjava/lang/Long.longValue:()J6:invokestatic#3//MethodgetValue:(J)J9:pop210:return}getValue方法调用对应main方法中编号为3和6的操作。数字3是命令invokevirtual是方法指令。对应的值为value.longValue,value对应声明的Long类型。也就是说,编译器将getValue(value)拆分成了两步。第一步是通过value的longValue方法对其进行拆箱,然后将拆箱结果传递给该方法。等同于:longprimitive=value.longValue();test(promitive);与原代码相比,如果值为null,调用longValue方法时会抛出NullPointerException。所以,本质上,所谓的自动拆箱和装箱只是Java提供的语法糖而已。再次确认下int类型的例子,确认自动拆箱和自动装箱这两个操作是如何工作的:publicclassIntBoxTest{publicstaticvoidmain(String[]args){Integerindex=11;intprimitive=index;}}还要检查上面的字节码code:publicclasscom.choupangxia.box.IntBoxTest{publiccom.choupangxia.box.IntBoxTest();Code:0:aload_01:invokespecial#1//Methodjava/lang/Object."":()V4:returnpublicstaticvoidmain(java.lang.String[]);代码:0:bipush112:invokestatic#2//Methodjava/lang/Integer.valueOf:(I)Ljava/lang/Integer;5:astore_16:aload_17:invokevirtual#3//Methodjava/lang/Integer.intValue:()I10:istore_211:return}可以看到main方法部分,数字2已经装箱,将原类型int装箱成Integer,调用的方法是Integer.valueOf;数字7进行拆箱操作,将Integer类型转换为int类型,调用的方法为Integer.intValue。自动拆箱装箱的本质通过上面的分析我们可以看出,所谓的拆箱装箱操作只是一种语法糖功能。编译器在编译运行时,本质上会调用相应包装类的不同方法进行处理。装箱时通常会调用包装类的valueOf方法,拆箱时通常会调用包装类的xxxValue()方法,其中xxx类似于boolean/long/int等。主要是自动拆箱和装箱的操作发生在赋值、比较、算术运算、方法调用等。此时,我们就需要解决主要的空指针问题。面试题看一个面试题:调用foo1和foo2时如何执行下面的?并简要分析一下。publicvoidfoo1(){if((Integer)null==1){}}publicvoidfoo2(){if((Integer)null>1){System.out.println("abc");}}显然在调用两个方法抛出空指针异常。抛空指针异常的原因和分析过程上面已经讲过了。您可以尝试分析字节码。再看一道面试题:下面的语句能正常执行吗?Integervalue1=(Integer)null;Doublevalue2=(Double)null;Booleanvalue3=(Boolean)null;答:可以正常执行。在Java中,null是一个特殊的值,可以赋值给任何引用类型,也可以转换为任何引用类型。总结任何一个小问题,小异常,深入追溯,不仅能更清楚地理解底层原理,而且在实践过程中也更有信心,少犯错误。

最新推荐
猜你喜欢