java中自动装箱和自动拆箱

来源:互联网 发布:淘宝的运费险是什么意思 编辑:程序博客网 时间:2024/05/16 13:56

装箱(Autoboxing)

          大家在平时编写Java程序时,都常常以以下方式来定义一个Integer对象:

[java] view plain copy
  1. Integer i=100;  

          从上面的代码中,大家可以得知,i为一个Integer类型的引用,100为Java中的基础数据类型(primitive data type)。而这种直接将一个基础数据类型传给其相应的封装类(wrapper class)的做法,便是自动装箱(Autoboxing)。

          在jdk 1.5中,自动装箱首次被引入。而在jdk 1.5之前,如果你想要定义一个value为100的Integer对象,则需要这样做:

[java] view plain copy
  1. Integer i=new Integer (100);  

原理

          我们在以上代码“Integer i=100;”处打一个断点,跟踪一下。

           

          接下来,我们可以看到,程序跳转到了Integer类的valueOf(int i)方法中

[java] view plain copy
  1. /** 
  2.      * Returns a <tt>Integer</tt> instance representing the specified 
  3.      * <tt>int</tt> value. 
  4.      * If a new <tt>Integer</tt> instance is not required, this method 
  5.      * should generally be used in preference to the constructor 
  6.      * {@link #Integer(int)}, as this method is likely to yield 
  7.      * significantly better space and time performance by caching 
  8.      * frequently requested values. 
  9.      * 
  10.      * @param  i an <code>int</code> value. 
  11.      * @return a <tt>Integer</tt> instance representing <tt>i</tt>. 
  12.      * @since  1.5 
  13.      */  
  14.     public static Integer valueOf(int i) {  
  15.         if(i >= -128 && i <= IntegerCache.high)  
  16.             return IntegerCache.cache[i + 128];  
  17.         else  
  18.             return new Integer(i);  
  19.     }  

          换句话说,装箱就是jdk自己帮你完成了调用Integer.valueOf(100)。

拆箱(Unboxing)

[java] view plain copy
  1. Integer integer100=100;  
  2. int int100=integer100;  

          从上面的代码中,大家可看出integer100为一个Integer类型的引用,int100为一个int类型的原始数据类型。但是,我们可以将一个Integer类型的对象赋值给其相应原始数据类型的变量。这便是拆箱。

          拆箱与装箱是相反的操作。装箱是将一个原始数据类型赋值给相应封装类的变量。而拆箱则是将一个封装类的变量赋值给相应原始数据类型的变量。装箱、拆箱的名字也取得相当贴切。

原理

          笔者相信大家也都猜到了,拆箱过程中jdk为我们做了什么。我们还是通过实验来证明我们的猜想吧。

          在以上代码的第二行代码打上断点,即在“int int100=integer100;”上打上断点,跟踪一下。

          我们可以看到,程序跳转到了Integer的intValue()方法。

[java] view plain copy
  1. /** 
  2.      * Returns the value of this <code>Integer</code> as an 
  3.      * <code>int</code>. 
  4.      */  
  5.     public int intValue() {  
  6.     return value;  
  7.     }  

          也就是,jdk帮我们完成了对intValue()方法的调用。对于以上的实验而言,便是调用integer100的intValue()方法,将其返回值赋给了int100。

实验1

[java] view plain copy
  1. Integer integer400=400;  
  2. int int400=400;  
  3. System.out.println(integer400==int400);  

          在以上代码的第三行中,integer400与int400执行了==运行。而这两个是不同类型的变量,到底是integer400拆箱了,还是int400装箱了呢?运行结果是什么呢?

          ==运算是判断两个对象的地址是否相等或者判断两个基础数据类型的值是否相等。所以,大家很容易推测到,如果integer400拆箱了,则说明对比的是两个基础类型的值,那此时必然相等,运行结果为true;如果int400装箱了,则说明对比的是两个对象的地址是否相等,那此时地址必然不相等,运行结果为false。(至于为什么笔者对它们赋值为400,就是后面将要讲到的陷阱有关)。

          我们实际的运行结果为true。所以是integer400拆箱了。对代码跟踪的结果也证明这一点。

实验2

[java] view plain copy
  1. Integer integer100=100;  
  2. int int100=100;  
  3. System.out.println(integer100.equals(int100));  

          在以上代码的第三行中,integer100的方法equals的参数为int100。我们知道equals方法的参数为Object,而不是基础数据类型,因而在这里必然是int100装箱了。对代码跟踪的结果也证明了这一点。

          其实,如果一个方法中参数类型为原始数据类型,所传入的参数类型为其封装类,则会自动对其进行拆箱;相应地,如果一个方法中参数类型为封装类型,所传入的参数类型为其原始数据类型,则会自动对其进行装箱。

实验3

[java] view plain copy
  1. Integer integer100 = 100;  
  2. int int100 = 100;  
  3. Long long200 = 200l;  
  4. System.out.println(integer100 + int100);  
  5. System.out.println(long200 == (integer100 + int100));  
  6. System.out.println(long200.equals(integer100 + int100));  

          在第一个实验中,我们已经得知,当一个基础数据类型与封装类进行==运算时,会将封装类进行拆箱。那如果+、-、*、/呢?我们在这个实验中,就可知道。

          如果+运算,会将基础数据类型装箱,那么:

  • 第4行中,integer100+int100就会得到一个类型为Integer且value为200的对象o,并执行这个对象的toString()方法,并输出”200”;
  • 第5行中,integer100+int100就会得到一个类型为Integer且value为200的对象o,==运算将这个对象与long200对象进行对比,显然,将会输出false;
  • 第6行中,integer100+int100就会得到一个类型为Integer且value为200的对象o,Long的equals方法将long200与o对比,因为两都是不同类型的封装类,因而输出false;

          如果+运算,会将封装类进行拆箱,那么:

  • 第4行中,integer100+int100就会得到一个类型为int且value为200的基础数据类型b,再将b进行装箱得到o,执行这个对象的toString()方法,并输出”200”;
  • 第5行中,integer100+int100就会得到一个类型为int且value为200的基础数据类型b1,==运算将long200进行拆箱得到b2,显然b1==b2,输出true;
  • 第6行中,integer100+int100就会得到一个类型为int且value为200的基础数据类型b,Long的equals方法将b进行装箱,但装箱所得到的是类型为Integer的对象o,因为o与long200为不同的类型的对象,所以输出false;

          程序运行的结果为:      

[java] view plain copy
  1. 200  
  2. true  
  3. false  

          因而,第二种推测是正确,即在+运算时,会将封装类进行拆箱。

陷阱

陷阱1

[java] view plain copy
  1. Integer integer100=null;  
  2. int int100=integer100;  

          这两行代码是完全合法的,完全能够通过编译的,但是在运行时,就会抛出空指针异常。其中,integer100为Integer类型的对象,它当然可以指向null。但在第二行时,就会对integer100进行拆箱,也就是对一个null对象执行intValue()方法,当然会抛出空指针异常。所以,有拆箱操作时一定要特别注意封装类对象是否为null。

陷阱2

[java] view plain copy
  1. Integer i1=100;  
  2. Integer i2=100;  
  3. Integer i3=300;  
  4. Integer i4=300;  
  5. System.out.println(i1==i2);  
  6. System.out.println(i3==i4);  

          因为i1、i2、i3、i4都是Integer类型的,所以我们想,运行结果应该都是false。但是,真实的运行结果为“System.out.println(i1==i2);”为 true,但是“System.out.println(i3==i4);”为false。也就意味着,i1与i2这两个Integer类型的引用指向了同一个对象,而i3与i4指向了不同的对象。为什么呢?不都是调用Integer.valueOf(int i)方法吗?

          让我们再看看Integer.valueOf(int i)方法。

[java] view plain copy
  1. /** 
  2.      * Returns a <tt>Integer</tt> instance representing the specified 
  3.      * <tt>int</tt> value. 
  4.      * If a new <tt>Integer</tt> instance is not required, this method 
  5.      * should generally be used in preference to the constructor 
  6.      * {@link #Integer(int)}, as this method is likely to yield 
  7.      * significantly better space and time performance by caching 
  8.      * frequently requested values. 
  9.      * 
  10.      * @param  i an <code>int</code> value. 
  11.      * @return a <tt>Integer</tt> instance representing <tt>i</tt>. 
  12.      * @since  1.5 
  13.      */  
  14.     public static Integer valueOf(int i) {  
  15.         if(i >= -128 && i <= IntegerCache.high)  
  16.             return IntegerCache.cache[i + 128];  
  17.         else  
  18.             return new Integer(i);  
  19.     }  

          我们可以看到当i>=-128且i<=IntegerCache.high时,直接返回IntegerCache.cache[i + 128]。其中,IntegerCache为Integer的内部静态类,其原码如下:

[java] view plain copy
  1. private static class IntegerCache {  
  2.         static final int high;  
  3.         static final Integer cache[];  
  4.   
  5.         static {  
  6.             final int low = -128;  
  7.   
  8.             // high value may be configured by property  
  9.             int h = 127;  
  10.             if (integerCacheHighPropValue != null) {  
  11.                 // Use Long.decode here to avoid invoking methods that  
  12.                 // require Integer's autoboxing cache to be initialized  
  13.                 int i = Long.decode(integerCacheHighPropValue).intValue();  
  14.                 i = Math.max(i, 127);  
  15.                 // Maximum array size is Integer.MAX_VALUE  
  16.                 h = Math.min(i, Integer.MAX_VALUE - -low);  
  17.             }  
  18.             high = h;  
  19.   
  20.             cache = new Integer[(high - low) + 1];  
  21.             int j = low;  
  22.             for(int k = 0; k < cache.length; k++)  
  23.                 cache[k] = new Integer(j++);  
  24.         }  
  25.   
  26.         private IntegerCache() {}  
  27.     }  

          我们可以清楚地看到,IntegerCache有静态成员变量cache,为一个拥有256个元素的数组。在IntegerCache中也对cache进行了初始化,即第i个元素是值为i-128的Integer对象。而-128至127是最常用的Integer对象,这样的做法也在很大程度上提高了性能。也正因为如此,“Integeri1=100;Integer i2=100;”,i1与i2得到是相同的对象。

          对比扩展中的第二个实验,我们得知,当封装类与基础类型进行==运行时,封装类会进行拆箱,拆箱结果与基础类型对比值;而两个封装类进行==运行时,与其它的对象进行==运行一样,对比两个对象的地址,也即判断是否两个引用是否指向同一个对象。


本博客中所有的博文都为笔者(Jairus Chan)原创。

如需转载,请标明出处:http://blog.csdn.net/JairusChan。

如果您对本文有任何的意见与建议,请联系笔者(JairusChan)。





  • 什么是自动装箱拆箱

基本数据类型的自动装箱(autoboxing)、拆箱(unboxing)是自J2SE 5.0开始提供的功能。 

一般我们要创建一个类的对象实例的时候,我们会这样:

 Class a = new Class(parameter);

 当我们创建一个Integer对象时,却可以这样:

 Integer i = 100; (注意:不是 int i = 100; )

实际上,执行上面那句代码的时候,系统为我们执行了:Integer i = Integer.valueOf(100); (感谢@黑面馒头 和 @MayDayIT 的提醒)

此即基本数据类型的自动装箱功能。

 
  • 基本数据类型与对象的差别 

基本数据类型不是对象,也就是使用int、double、boolean等定义的变量、常量。

基本数据类型没有可调用的方法。

eg:  int t = 1;     t.  后面是没有方法滴。

 Integer t =1; t.  后面就有很多方法可让你调用了。

 
  • 什么时候自动装箱

例如:Integer i = 100;

相当于编译器自动为您作以下的语法编译:Integer i = Integer.valueOf(100);

 
  • 什么时候自动拆箱

  自动拆箱(unboxing),也就是将对象中的基本数据从对象中自动取出。如下可实现自动拆箱:

1 Integer i 10//装箱 
2  int t = i; //拆箱,实际上执行了 int t = i.intValue();

  在进行运算时,也可以进行拆箱。 

1 Integer i 10
2 System.out.println(i++);

 

  • Integer的自动装箱

复制代码
//在-128~127 之外的数
Integer i1 =200;
Integer i2
=200;
System.out.println(
"i1==i2: "+(i1==i2));
// 在-128~127 之内的数
Integer i3 =100;
Integer i4
=100;
System.out.println(
"i3==i4: "+(i3==i4));
复制代码
    输出的结果是:
    i1==i2: false
i3
==i4: true

 说明:

equals() 比较的是两个对象的值(内容)是否相同。

"==" 比较的是两个对象的引用(内存地址)是否相同,也用来比较两个基本数据类型的变量值是否相等。

 

前面说过,int 的自动装箱,是系统执行了 Integer.valueOf(int i),先看看Integer.java的源码:

1
2
3
4
5
6
public static Integer valueOf(int i) {
    if(i >= -128 && i <= IntegerCache.high)  // 没有设置的话,IngegerCache.high 默认是127
        return IntegerCache.cache[i + 128];
    else
        return new Integer(i);
}

  

对于–128到127(默认是127)之间的值,Integer.valueOf(int i) 返回的是缓存的Integer对象(并不是新建对象)

所以范例中,i3 与 i4实际上是指向同一个对象。

而其他值,执行Integer.valueOf(int i) 返回的是一个新建的 Integer对象,所以范例中,i1与i2 指向的是不同的对象。

当然,当不使用自动装箱功能的时候,情况与普通类对象一样,请看下例:

 

1 Integer i3 =new Integer(100); 
2 Integer i4 =new Integer(100); 
3 System.out.println("i3==i4: "+(i3==i4));//显示false

(感谢易之名的提醒O(∩_∩)O~)

 

  • String 的拆箱装箱

先看个例子:

复制代码
1 String str1 ="abc";
2 String str2 ="abc";
3 System.out.println(str2==str1); //输出为 true 
4 System.out.println(str2.equals(str1)); //输出为 true 
5   
6 String str3 =new String("abc");
7 String str4 =new String("abc"); 
8 System.out.println(str3==str4); //输出为 false 
9 System.out.println(str3.equals(str4)); //输出为 true
复制代码

    这个怎么解释呢?貌似看不出什么。那再看个例子。

1 String d ="2"
2 String e ="23";
3 = e.substring(01);
4 System.out.println(e.equals(d)); //输出为 true 
5 System.out.println(e==d); //输出为 false
第二个例子中,e的初始值与d并不同,因此e与d是各自创建了个对象,(e==d)为false 。
同理可知,第一个例子中的str3与str4也是各自new了个对象,而str1与str2却是引用了同一个对象。
0 0