Java中的装箱与拆箱

简介:

J2SE5.0后推出了自动装箱和拆箱的功能,以提高我们的开发效率,然而自动装箱和拆箱实际上是通过编译器来支持的(并非语言本身,或者说虚拟机),因而这种支持也隐藏了部分内部实质,再加上某些类的优化(比如Integer里面的缓存等,参看关于缓存节),就更加容易在特定的环境下产生问题,并且如果不知道原来还无法调试。以下先是简单的介绍了编译器对装箱和拆箱的实现,并根据实现简单介绍一下可能会遇到的几个问题。

装箱和拆箱实现

以下装箱和拆箱代码:

       Object value  =   10 ;
       
int  intValue  =  (Integer)value;
       Integer newIntValue 
=   new  Integer( 10 );

 

编译成字节码如下:

     0 bipush 10

     2 invokestatic java.lang.Integer.valueOf(int) : java.lang.Integer [20]

     5 astore_1 [value]

     6 aload_1 [value]

     7 checkcast java.lang.Integer [21]

    10 invokevirtual java.lang.Integer.intValue() : int [26]

    13 istore_2 [intValue]

    14 new java.lang.Integer [21]

    17 dup

    18 bipush 10

    20 invokespecial java.lang.Integer(int) [30]

    23 astore_3 [newIntValue]

从以上字节码可以看到10首先调用valueOf方法转换为Integer实例,再赋值该value,而value强制转换成Integer类后,会调用intValue方法,后赋值给intValue。这就是用编译器来实现装箱和拆箱。

 

奇怪的NullPointerException

查看以下代码:

       Integer value  =   null ;
       
int  intValue  =  value;

 

可以编译通过,但是运行的时候却会发生NullPointerException。这是由什么引起的呢?依然看一下字节码就可以了:

     0 aconst_null

     1 astore_1 [value]

     2 aload_1 [value]

    3 invokevirtual java.lang.Integer.intValue() : int [20]

     6 istore_2 [intValue]

从字节码中可以看到,从value赋值该intValue事实上是直接在value实例上调用intValue函数。

对当前代码,我们可以一眼就看出当前valuenull的问题,但是如果这个null是在很远以外的地方赋值的呢?或者是间接赋值呢?这个时候遇到这种问题就会比较诡异了。

 

相等与不相等问题

查看一下代码:

       Integer value1  =   100 ;
       Integer value2 
=   100 ;
       System.out.println(
" value1 == value2 is  "   +  (value1  ==  value2));
      
       Integer value3 
=   200 ;
       Integer value4 
=   200 ;
       System.out.println(
" value3 == value4 is  "   +  (value3  ==  value4));

 

这段代码会是什么结果?

value1 == value2 is true

value3 == value4 is false

 

两段代码就是值不一样,其他的都一样,竟然会有区别?这个奥妙就因为装箱过程中调用的是valueOf方法,而valueOf方法对值在-128127之间的数值缓存了(参见关于缓存一节),因而value1value2的引用是相同的,而value3value4的引用是不一样的,而==比较的是引用,因而才会出现以上的结果。

这确的做法应该是:

       Integer value1  =   100 ;
       Integer value2 
=   100 ;
       System.out.println(
" value1 == value2 is  "   +  (value1.equals(value2)));
      
       Integer value3 
=   200 ;
       Integer value4 
=   200 ;

       System.out.println("value3 == value4 is " + (value3.equals(value4))); 

这样的结果就是预料的结果了:

value1 == value2 is true

value3 == value4 is true

 

所以我们要慎用“==”操作符。

 

String中的相等与不等

String中也有类似的情况,查看一下代码:

       String str1  =   " abc " ;
       String str2 
=   " abc " ;
       System.out.println(
" str1 == str2 is  "   +  (str1  ==  str2));
      
       String str3 
=   new  String( " abc " );
       String str4 
=   new  String( " abc " );
       System.out.println(
" str3 == str4 is  "   +  (str3  ==  str4));

 

执行结果:

str1 == str2 is true

str3 == str4 is false

 

这是因为str1str2使用的是同一个字符串,即在字符常量中的字符串,而str3str4在使用字符常量中的字符为参数又创建出了两个新的字符串对象,因而在引用比较情况下是不等的。我们可以从字节码中得到这些信息(删除打印的代码):

     0 ldc <String "abc"> [20]

     2 astore_1 [str1]

     3 ldc <String "abc"> [20]

     5 astore_2 [str2]

     6 new java.lang.String [22]

     9 dup

    10 ldc <String "abc"> [20]

    12 invokespecial java.lang.String(java.lang.String) [24]

    15 astore_3 [str3]

    16 new java.lang.String [22]

    19 dup

    20 ldc <String "abc"> [20]

    22 invokespecial java.lang.String(java.lang.String) [24]

    25 astore 4 [str4]

正确的做法还是调用equals方法,而不是使用“==”操作符。

 

关于缓存

据目前信息,有缓存的类有:ByteShortIntegerLong以及Boolean类。而这种缓存也只是在调用valueOf(静态)方法的时候才会存在(装箱正是调用了valueOf方法)。对整型,缓存的值都是-128127(包括-128127)之间,其他值都不缓存,而对Boolean类型只有truefalse值。代码如下:

public final class Integer extends Number {
    
public static Integer valueOf(int i) {
        
if(i >= -128 && i <= IntegerCache.high)
            
return IntegerCache.cache[i + 128];
        
else
        return new Integer(i);
}
public final class Boolean {
    
public static Boolean valueOf(boolean b) {
        
return (b ? TRUE : FALSE);
    }

相关文章
|
8月前
|
Java 编译器
Java中的自动装箱和拆箱
Java中的自动装箱和拆箱
|
7月前
|
缓存 Java 编译器
Java自动装箱与拆箱(AutoBoxing_UnBoxing)
Java自动装箱与拆箱(AutoBoxing_UnBoxing)
60 4
|
7月前
|
Java 开发者
java自动装箱与拆箱
java自动装箱与拆箱
|
7月前
|
存储 缓存 Java
Java自动装箱和拆箱
Java自动装箱和拆箱
37 0
|
IDE Java 编译器
Java中自动装箱和拆箱
装箱(Boxing),也称为包装(Wrapper),是在对象中放置原语类型(primitive type)的过程,以便原语(primitive)可以作为引用对象使用。 这里的`primitive type`就是Java里面的基本类型,所有的基本类型都有一个与之对应的类。例如,Integer类对应基本类型int。 通常,这些类称为包装器(wrapper)。这些对象包装器类拥有很明显的名字:Integer、Long、Float、Double、Short、Byte、Character、Void和Boolean(前6个类派生于公共的超类Number)。
|
Java
Java中的拆箱与装箱
Java中的拆箱与装箱
152 1
|
Java 程序员 编译器
|
Java
Java 自动拆箱装箱原理
Java 自动拆箱装箱原理
122 0
Java 自动拆箱装箱原理
|
Java
Java - 装箱 / 拆箱(上)
Java - 装箱 / 拆箱(上)
65 0
Java - 装箱 / 拆箱(上)
|
Java
Java - 装箱 / 拆箱(下)
Java - 装箱 / 拆箱(下)
103 0