Java 自动装箱和拆箱

来源:互联网 发布:surface studio 知乎 编辑:程序博客网 时间:2024/05/21 10:33

转自http://blog.sina.com.cn/s/blog_716358dc0100lj5e.html

在JAVA中,在比较两个对象的值是否相等时,我们都知道使用object.equals method,而不能使用"=="操作符进行比较,因为"=="比较的是object的内存地址,通过new创建的object的内存地址是不同的(当然,如果将一个对象指向另一个对象操作另当别论)。
    比如:
    Integer i1=new Integer(200);
    Integer i2=new Integer(200);
    此时i1和i2引用了不同的内存区域,所以“i1==i2”的比较结果是false,“i1.equals(i2)”的结果是true。
    因为Java使用了7中内置类型,这些内置类型都有与之对应的类,在内置类型可以与其对应的类之间进行自动转换,Java称其为“Autoboxing”。例如:
    Integer i3=200;   
    Integer i4=200;
    这次对i3和i4进行比较是遵循上面i1和i2的比较规则的。那么好,再看看下面的例子:
    Integer i5=100;   
    Integer i6=100;
    i5和i6进行"=="时的比较结果竟然是true,“i5.equals(i6)”的结果也是true。对于equals导致的放回结果没有什么惊奇,但是为什么"=="的结果是true呢?
    这个问题涉及到两个方面问题:Autoboxing的实现机制和Integer的CacheInteger的机制。
    1.Autoboxing的实现机制
        Java的Autoboxing的实现机制是在对内置类型对应的类的"="运算符进行了重载,当没有使用new关键字创建对象时,Java就使用Autoboxing方式创建对象,"="重载对应着类的valueOf(parameter) method,parameter为其内置类型,比如Integer的valueOf method的parameter对应的就是内置类型int。这就是Java的Autoboxing的实现机制。看文档的解释:
  Integer的valueOf()方法:因为该方法有可能通过缓存经常请求的值而显著提高空间和时间性能。 
    2.Integer的CacheInteger的机制
        当理解了Java的Autoboxing机制之后,我们看看Integer的valueOf(int) method是如何实现的。(JDK1.6 Integer.java)
        public static Integer valueOf(int i) {
         final int offset = 128;
         if (i >= -128 && i <= 127) { // must cache
             return IntegerCache.cache[i + offset];
         }
         return new Integer(i);
         }       
        从以上代码可以清晰的看到,当没有使用new创建Integer object的时候,如果object的值在-128至127之间,返回的则是一个IntegerCache类的cache数组的一个元素(当然这个元素也是Integer类型的了),而不是new一个Integer object。再来看看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);
             }
        }
        看到这里上面问题的答案已经出来了,原来Java的Integer class中有一个static的IntegerCache class,这个static class中的构造方法中创建了一个cache的Integer array,范围从-128至127。在不使用new关键字创建Integer类型的object的时候,如果这个Integer的范围在-128至127之间,则使用的是Integer的IntegerCache的cache array的一个Integer元素。如果不在这个范围,则调用Integer class的构造方法创建一个Integer object。
        所以上面i5和i6都引用了IntegerCache这个static class,这时在使用"=="进行比较的时候,i5和i6当然是相等的了。
        研究这个问题是因为在前几次上Java课时,老师将到了这个现象,但是并没有对此进行解决,有的同学怀疑是Java的bug,我感觉不像,因为Java不应该出现如此低级的bug。所以自己进行了一些测试,开始发现这个问题与值的大小有关,将值的范围逐步缩小,发现这个问题只有在-128之127之间有关(正好是1个带符号的byte),当时怀疑是java为了效率问题使用了一个byte表示值比较小的Integer object,但是后来通过debug形式,发现-128至127的Integer object的id竟然使用了一个id,也就是使用了同一个object。再后来就是查看了Integer的源码,再后来就是这篇文章了。

转自http://blog.csdn.net/gtuu0123/article/details/6116683

以下这段代码输出什么:

[java] view plaincopy
  1. package Test;  
  2. public class Test2 {  
  3.     public static void main(String[] args) {  
  4.         Integer i = 100;  
  5.         Integer j = 200;  
  6.         Integer ii = 100;  
  7.         Integer jj = 200;  
  8.         System.out.println(i == ii);  
  9.         System.out.println(j == jj);  
  10.     }  
  11.       
  12. }  

实际运行了一下:true,false


原因是:在100自动装箱成Integer时,调用的是Integer.valueOf(int i)这个方法,而在-128到127间是有缓存的

[java] view plaincopy
  1. public static Integer valueOf(int i) {  
  2.     final int offset = 128;  
  3.     if (i >= -128 && i <= 127) { // must cache   
  4.         return IntegerCache.cache[i + offset];  
  5.     }  
  6.         return new Integer(i);  
  7.     }  
  8. private static class IntegerCache {  
  9.     private IntegerCache(){}  
  10.     static final Integer cache[] = new Integer[-(-128) + 127 + 1];  
  11.     static {  
  12.         for(int i = 0; i < cache.length; i++)  
  13.         cache[i] = new Integer(i - 128);  
  14.     }  
  15.     }  

因此,Integer i = 100和Integer j = 100会返回同一个实例,引用相同,故而为true;但是200不在-128到127之间,因此返回的引用是不同的,故而为false。
但是那位同学又问我,为什么在自动装箱时调用的是Integer.valueOf(int i),这个我一时真没想出来。后来突然间大悟,因为自动装箱是编译时的行为 ,为何我不先将代码编译,然后用javap将其恢复成jvm指令呢?
代码如下:
[java] view plaincopy
  1. package Test;  
  2. public class Test1 {  
  3.       
  4.     public static void main(String[] args) {  
  5.         Integer i = 100;  
  6.         Integer j = 200;  
  7.     }  
  8.       
  9. }  

javap -c Test/Test1 > temp,得到如下结果:
[java] view plaincopy
  1. Compiled from "Test1.java"  
  2. public class Test.Test1 extends java.lang.Object{  
  3. public Test.Test1();  
  4.   Code:  
  5.    0:   aload_0  
  6.    1:   invokespecial   #8//Method java/lang/Object."<init>":()V  
  7.    4:   return  
  8. public static void main(java.lang.String[]);  
  9.   Code:  
  10.    0:   bipush  100  
  11.    2:   invokestatic    #16//Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;  
  12.    5:   astore_1  
  13.    6:   sipush  200  
  14.    9:   invokestatic    #16//Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;  
  15.    12:  astore_2  
  16.    13:  return  
  17. }  

可以看出,在自动装箱时的确调用的是Integer.valueOf(int i)


转自http://www.cnblogs.com/yakov/archive/2011/10/28/java_di_shou_boxing.html

Autoboxing和unboxing又名拆箱和装箱,简单一点讲,就是从primitive转换到wrapper class,例如int类型到Integer类型就是装箱,而Integer类型到int类型则是拆箱。当然,这里的装箱和拆箱都是auto的,是JVM在工作的内容,事实上不用我们手写,然而也有手写的对应方式,如下所示:

1 int i=10;
2 Integer a=new Integer(i);//装箱的操作
3 int j=a.intValue();//拆箱的操作

上面是手动的,在Java5.0之后已经在JVM中有了自动的装箱和拆箱的转换,如下所示:

 

1 int i=10;
2 Integer b=i;//自动的装箱
3 int k=b;//自动的拆箱

装箱和拆箱就是这么简单,下面可以看一下自增是怎么一个过程,这是一个很有意思的事情,递减也是一样。

1 Integer d=new Integer(10);
2 d++;//这条语句使得d先拆箱,然后进行++操作,而后对结果再装箱

上面的这条语句,使得Java保证了wrapper class也可以是正常使用通用的操作符,但这绝对不是C++中的运算符重载。你还可以试着分析三元表达式和条件表达式中的装箱拆箱过程。

 

这里需要注意的一点就是装箱和拆箱在Method Overload时候的问题,例如下面:

 

1 public void doSomething(double num);
2 public void doSomething(Integer num);

在一个类中有这么两个函数,那么对于整数int k,如果进行doSomething(k)的调用,会调用哪个呢?

在Java1.4中,显然是调用double类型的函数,然而,现在有了自动的拆箱和装箱之后会调用哪个呢?还double类型的函数,这样就保证了以前的代码在现在的版本中也可以正确的运行。

 

注意,Java5中,Method的解析是三个pass的过程:

A.编译器会试着不用任何的boxing和unboxing,或者启用vararg来定位正确的method。这会找到根据Java1.4的规则而会调用的任何method。

B.如果第一个pass失败了,编译器会再度尝试解析method,但这次会允许boxing和unboxing转换。具有vararg的method不在这次pass的考虑中。

C.如果第二个pass也失败了,编译器会做最后一次的尝试,允许boxing和unboxing,且同时也考虑到vararg method。


0 0
原创粉丝点击