您的位置:首页 > 编程语言 > Java开发

Java中的装箱与拆箱

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

在Java SE5之前,如果要生成一个数值为10的Integer对象,必须这样进行:

Integer i = new Integer(10);
 而在从Java SE5开始就提供了自动装箱的特性,如果要生成一个数值为10的Integer对象,只需要这样就可以了:

Integer i = 10;


 这个过程中会自动根据数值创建对应的 Integer对象,这就是装箱。

  那什么是拆箱呢?顾名思义,跟装箱对应,就是自动将包装器类型转换为基本数据类型:
Integer i = 10;  //装箱
int n = i;   //拆箱


 简单一点说,装箱就是 自动将基本数据类型转换为包装器类型;拆箱就是 自动将包装器类型转换为基本数据类型。

  下表是基本数据类型对应的包装器类型:
int(4字节)Integer
byte(1字节)Byte
short(2字节)Short
long(8字节)Long
float(4字节)Float
double(8字节)Double
char(2字节)Character
boolean(未定)Boolean


装箱和拆箱实现

以下装箱和拆箱代码:
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函数。

对当前代码,我们可以一眼就看出当前value是null的问题,但是如果这个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方法对值在-128到127之间的数值缓存了(参见关于缓存一节),因而value1和value2的引用是相同的,而value3和value4的引用是不一样的,而==比较的是引用,因而才会出现以上的结果。

正确的做法应该是:
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



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

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方法,而不是使用“==”操作符。



关于缓存

据目前信息,有缓存的类有:Byte、Short、Integer、Long以及Boolean类。而这种缓存也只是在调用valueOf(静态)方法的时候才会存在(装箱正是调用了valueOf方法)。对整型,缓存的值都是-128到127(包括-128和127)之间,其他值都不缓存,而对Boolean类型只有true和false值。代码如下:
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);
    }

IntegerCache的实现:

private static class IntegerCache {  
private IntegerCache(){}  
  
static final Integer cache[] = new Integer[-(-128) + 127 + 1];  
  
static {  
    for(int i = 0; i < cache.length; i++)  
    cache[i] = new Integer(i - 128);  
}  
   }
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: