Java自动拆箱空指针异常的解决

 更新时间:2021年03月04日 10:05:34   作者:secbro2  
这篇文章主要介绍了Java自动拆箱空指针异常的解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

公司搬迁,临时充当装修工,提前两个小时到公司忙着拆卸设备。结果接到客户反映,某部分功能偶尔不能用。于是参与救火,与写这段代码的小伙伴一起排查原因。

最终发现导致业务偶尔不能使用是由Long类型自动拆箱导致空指针异常引起的。下面就带大家分析一下Java中基础类型的包装类在拆箱和装箱过程中都做了什么,为什么会出现空指针异常,以及面试过程中会出现的相关面试题。

问题重现

下面通过一个简单的示例才重现一下异常出现的场景。

public class BoxTest {

  public static void main(String[] args) {
    Map<String,Object> result = httpRequest();
    long userId = (Long) result.get("userId");
  }

  // 模拟一个HTTP请求
  private static Map<String,Object> httpRequest(){
    Map<String,Object> map = new HashMap<>();
    map.put("userId",null);
    return map;
  }
}

基本的场景就是请求一个接口,去接口中取某个值,这个值为Long类型,从Map中取得值之后,进行Long类型的强转。当接口返回的userId为null时,强转这块就抛出空指针异常:

Exception in thread "main" java.lang.NullPointerException
 at com.choupangxia.box.BoxTest.main(BoxTest.java:15)

上面的场景跟下面的代码出现异常效果一样:

public class BoxTest {

  public static long getValue(long value) {
    return value;
  }

  public static void main(String[] args) {
    Long value = null;
    getValue(value);
  }
}

上述代码也是将Long类型进拆箱导致的异常,只不过一个在代码中,一个在参数中。为了分析更简化,我们以第二个为例进行讲解。

原因分析

最初大家可能会疑惑,抛出异常的代码都没有对象的方法调用,怎么会出现空指针呢?

这中间主要涉及到的就是一个自动拆箱操作。是否是拆箱导致的呢?我们来通过字节码看一下。

通过javap -c来查看一下对应的字节码:

public class com.choupangxia.box.BoxTest {
 public com.choupangxia.box.BoxTest();
  Code:
    0: aload_0
    1: invokespecial #1         // Method java/lang/Object."<init>":()V
    4: return

 public static long getValue(long);
  Code:
    0: lload_0
    1: lreturn

 public static void main(java.lang.String[]);
  Code:
    0: aconst_null
    1: astore_1
    2: aload_1
    3: invokevirtual #2         // Method java/lang/Long.longValue:()J
    6: invokestatic #3         // Method getValue:(J)J
    9: pop2
   10: return
}

其中getValue方法调用对应的是main方法中编号3和6的操作。编号3为命令invokevirtual为方法指令。对应的便是value.longValue,value对应的就是声明的Long类型。

也就是说编译器将getValue(value)拆分成了两步,第一步将通过value的longValue方法将其拆箱,然后再将拆箱之后的结果传递给方法。相当于:

long primitive = value.longValue();
test(promitive);

对照最开始的代码,如果value为null的话,那么在调用longValue方法时便会抛出NullPointerException。
所以,本质上来讲,所谓的自动拆箱和装箱只不过是Java提供的语法糖而已。

再次证实

下面用int类型的实例同时证实一下自动拆箱和自动装箱两个操作语法糖底层到底是怎么运行的:

public class IntBoxTest {

  public static void main(String[] args) {
    Integer index = 11;
    int primitive = index;
  }
}

同样查看上面代码的字节码:

public class com.choupangxia.box.IntBoxTest {
 public com.choupangxia.box.IntBoxTest();
  Code:
    0: aload_0
    1: invokespecial #1         // Method java/lang/Object."<init>":()V
    4: return

 public static void main(java.lang.String[]);
  Code:
    0: bipush    11
    2: invokestatic #2         // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
    5: astore_1
    6: aload_1
    7: invokevirtual #3         // Method java/lang/Integer.intValue:()I
   10: istore_2
   11: return
}

可以看到main方法部分,编号2进行了装箱操作,将原始类型int,装箱成了Integer,调用的方法为Integer.valueOf;而编号7进行了拆箱操作将Integer类型转换成了int类型,调用的方法为Integer.intValue。

自动拆箱装箱的本质

通过上面的分析,我们可以看出所谓的拆箱(unboxing)和装箱(boxing)操作只不过是一个语法糖的功能。编译器在编译操作时,本质上还是会调用对应包装类的不同方法来进行处理。
装箱时通常会调用包装类的valueOf方法,而拆箱时通常会调用包装类的xxxValue()方法,其中xxx为类似boolean/long/int等。
而自动拆箱和装箱的操作主要发生在赋值、比较、算数运算、方法调用等常见。此时,我们就需要主要空指针的问题。

面试题

看一个面试题:请问下面foo1和foo2被调用时如何执行?并简单分析一下。

public void foo1() {
  if ((Integer) null == 1) {
  }
}

public void foo2() {
  if ((Integer) null > 1) {
    System.out.println("abc");
  }
}

很明显在调用两个方法时都会抛出空指针异常。关于抛空指针异常的原因及分析过程,上文已经讲过,大家可以尝试分析一下字节码。

再看一个面试题:下面的语句能正常执行吗?

Integer value1 = (Integer) null;
Double value2 = (Double) null;
Boolean value3 = (Boolean) null;

答案:可以正常执行。在Java中null是一个特殊的值,可以赋值给任何引用类型,也可以转化为任何引用类型。

小结

任何一个小的问题,小的异常,如果深入追踪一下,不仅能够更清楚的明白底层原理,而且还可以在实践的过程中更有把握,更少犯错。

到此这篇关于Java自动拆箱空指针异常的解决的文章就介绍到这了,更多相关Java自动拆箱空指针异常内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家! 

相关文章

  • Java BigDecimal基础用法详解

    Java BigDecimal基础用法详解

    Java在java.math包中提供的API类BigDecimal,用来对超过16位有效位的数进行精确的运算。双精度浮点型变量double可以处理16位有效数,但在实际应用中,可能需要对更大或者更小的数进行运算和处理
    2022-06-06
  • java中String与StringBuilder的区别

    java中String与StringBuilder的区别

    本篇文章介绍了,java中String与StringBuilder的区别。需要的朋友参考下
    2013-04-04
  • MybatisPlus分页失效不起作用的解决

    MybatisPlus分页失效不起作用的解决

    在使用MybatisPlus的selectPage时发现分页不起作用,每次返回的都是全部的数据,本文就来介绍一下MybatisPlus分页失效不起作用的解决,感兴趣的可以了解一下
    2024-03-03
  • spring boot基于Java的容器配置讲解

    spring boot基于Java的容器配置讲解

    这篇文章主要介绍了spring boot基于Java的容器配置讲解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-04-04
  • MyBatis常见报错问题及解决方案

    MyBatis常见报错问题及解决方案

    这篇文章主要介绍了MyBatis常见报错问题及解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-11-11
  • Java构造函数里的一些坑记录super()和this()

    Java构造函数里的一些坑记录super()和this()

    这篇文章主要介绍了Java构造函数里的一些坑记录super()和this(),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-03-03
  • 一文详解Reactor模型与实现示例

    一文详解Reactor模型与实现示例

    这篇文章主要为大家介绍了Reactor模型与实现示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-03-03
  • 阿里nacos+springboot+dubbo2.7.3统一处理异常的两种方式

    阿里nacos+springboot+dubbo2.7.3统一处理异常的两种方式

    本文主要介绍了阿里nacos+springboot+dubbo2.7.3统一处理异常的两种方式,文中根据实例编码详细介绍的十分详尽,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-03-03
  • SpringBoot执行有返回值的异步任务问题

    SpringBoot执行有返回值的异步任务问题

    这篇文章主要介绍了SpringBoot执行有返回值的异步任务问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-07-07
  • 基于Java ORM框架的使用详解

    基于Java ORM框架的使用详解

    本篇文章是对Java中ORM框架的使用进行了详细的分析介绍,需要的朋友参考下
    2013-05-05

最新评论