首页 语言 Java中基础类型的包装类在拆箱和装箱过程中起到了什么用处

Java中基础类型的包装类在拆箱和装箱过程中起到了什么用处

副标题#e#

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

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 

#p#副标题#e#

       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 

#p#副标题#e##p#分页标题#e#

       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等。

而自动拆箱和装箱的操作主要发生在赋值、比较、算数运算、方法调用等常见。此时,我们就需要主要空指针的问题。

关于作者: dawei

【声明】:乐山站长网内容转载自互联网,其相关言论仅代表作者个人观点绝非权威,不代表本站立场。如您发现内容存在版权问题,请提交相关链接至邮箱:bqsm@foxmail.com,我们将及时予以处理。

热门文章