Java 包装类 拆箱 装箱

   Java语言是一个面向对象的语言,但是Java中的基本数据类型却是不面向对象的。基本类型的数据不是对象,所以对于要将数据类型作为对象来使用的情况,java提供了相对应的包装类。java中数据类型总共可分为两大种,基本数据类型(值类型)和类类型(引用数据类型)。

         包装类均位于java.lang包,包装类和基本数据类型的对应关系如下表所示:

包装类对应表

基本型别 包装类 大小 最小值 最大值
boolean Boolean ----- ----- ------
char Character 16-bit Unicode 0 Unicode 2^16-1
byte Byte 8-bit -128 127
short Short 16-bit -32768 32767
int Integer 32-bit -2.1E+09 2.15E+09
long Long 64-bit -9.2E+18 9.22E+18
float Float 32-bit IEEE754 IEEE754
double Double 64-bit IEEE754 IEEE754

 

         在这八个类名中,除了Integer和Character类以后,其它六个类的类名和基本数据类型一致,只是类名的第一个字母大写即可。

         对于包装类说,这些类的用途主要包含两种:a、作为和基本数据类型对应的类类型存在,方便涉及到对象的操作。 b、包含每种基本数据类型的相关属性如最大值、最小值等,以及相关的操作方法。

 

基本类型的包装类

Integer 、Long、Short、Byte、Character、Double、Float、Boolean、BigInteger、BigDecmail

其中BigInteger、BigDecimal没有相对应的基本类型,主要应用于高精度的运算,BigInteger 支持任意精度的整数,BigDecimal支持任意精度带小数点的运算。

 

基本类型与包装类型的异同:

1、在Java中,一切皆对象,但八大基本类型却不是对象。

2、声明方式的不同,基本类型无需通过new关键字来创建,而封装类型需new关键字。

3、存储方式及位置的不同,基本类型是直接存储变量的值保存在堆栈中能高效的存取,封装类型需要通过引用指向实例,具体的实例保存在堆中。

4、初始值的不同,封装类型的初始值为null,基本类型的的初始值视具体的类型而定,比如int类型的初始值为0,boolean类型为false;

5、使用方式的不同,比如与集合类合作使用时只能使用包装类型。

 

 由于八个包装类的使用比较类似,下面以最常用的Integer类为例子介绍包装类的实际使用。

 1、实现int和Integer类之间的转换
在实际转换时,使用Integer类的构造方法和Integer类内部的intValue方法实现这些类型之间的相互转换,实现的代码如下:
     int n = 10;
     Integer in = new Integer(100);
     //将int类型转换为Integer类型
     Integer in1 = new Integer(n);
     //将Integer类型的对象转换为int类型
     int m = in.intValue();
 2、Integer类内部的常用方法
 在Integer类内部包含了一些和int操作有关的方法,下面介绍一些比较常用的方法:
   a、parseInt方法
       public static int parseInt(String s)
该方法的作用是将数字字符串转换为int数值。在以后的界面编程中,将字符串转换为对应的int数字是一种比较常见的操作。使用示例如下:
       String s = “123”;
       int n = Integer.parseInt(s);
则int变量n的值是123,该方法实际上实现了字符串和int之间的转换,如果字符串都包含的不是都是数字字符,则程序执行将出现异常。(说明:异常的概念将在下一章进行讲述)
另外一个parseInt方法:
         public static int parseInt(String s, int radix)
则实现将字符串按照参数radix指定的进制转换为int,使用示例如下:
         //将字符串”120”按照十进制转换为int,则结果为120
         int n = Integer.parseInt(“120”,10);
         //将字符串”12”按照十六进制转换为int,则结果为18
         int n = Integer.parseInt(“12”,16);
         //将字符串”ff”按照十六进制转换为int,则结果为255
         int n = Integer.parseInt(“ff”,16);
这样可以实现更灵活的转换。
     b、toString方法
          public static String toString(int i)
          该方法的作用是将int类型转换为对应的String类型。
          使用示例代码如下:
             int m = 1000;
             String s = Integer.toString(m);
          则字符串s的值是”1000”。
          另外一个toString方法则实现将int值转换为特定进制的字符串:
             public static int parseInt(String s, int radix)
          使用示例代码如下:
             int m = 20;
             String s = Integer.toString(m);
          则字符串s的值是”14”。
         其实,JDK自从1.5(5.0)版本以后,就引入了自动拆装箱的语法,也就是在进行基本数据类型和对应的包装类转换时,系统将自动进行,这将大大方便程序员的代码书写。使用示例代码如下:
          //int类型会自动转换为Integer类型
          int m = 12;
          Integer in = m;
          //Integer类型会自动转换为int类型
          int n = in;
         所以在实际使用时的类型转换将变得很简单,系统将自动实现对应的转换。

 

由此引出包装类的装箱和拆箱

所谓装箱,就是把基本类型用它们相对应的引用类型包起来,使它们可以具有对象的特质,如我们可以把int型包装成Integer类的对象,或者把double包装成Double,等等。 

所谓拆箱,就是跟装箱的方向相反,将Integer及Double这样的引用类型的对象重新简化为值类型的数据 



J2SE5.0后提供了自动装箱与拆箱的功能,此功能事实上是编译器来帮您的忙,编译器在编译时期依您所编写的方法,决定是否进行装箱或拆箱动作。 

自动装箱的过程:每当需要一种类型的对象时,这种基本类型就自动地封装到与它相同类型的包装中。 

自动拆箱的过程:每当需要一个值时,被装箱对象中的值就被自动地提取出来,没必要再去调用intValue()和doubleValue()方法。 



自动装箱,只需将该值赋给一个类型包装器引用,java会自动创建一个对象。

Integer i=100;//没有通过使用new来显示建立,java自动完成。 

自动拆箱,只需将该对象值赋给一个基本类型即可。例如:int j=i; 

int i = 10;
Integer j =new Integer(i); //手动装箱操作  
int k = j.intValue();      //手动拆箱操作  

int i = 11;
Integer j = i; //自动装箱,在编译器中作了判断,如果右边数字,左边为Integer,自动将上边代码变为Integer a =new Integer(i);
int k = j //自动拆箱
 
  int c = new Integer(1500);//自动拆箱,等价于int c = new Integer(1500).intValue();

  int d = b;//自动拆箱,编译器改进为b.intValue()

 

然而在Integer的自动装拆箱会有些细节值得注意: 

public static void main(String[] args) {
        Integer a=100;
        Integer b=100;
        
        Integer c=200;
        Integer d=200;
        
        System.out.println(a==b);   //1
        System.out.println(a==100); //2
        
        System.out.println(c==d);   //3
        System.out.println(c==200); //4
   }

在java中,"=="是比较object的reference而不是value,自动装箱后,abcd都是Integer这个Oject,因此“==”比较的是其引用。按照常规思维,1和3都应该输出false。但结果是: 
true 
true 
false 
true 

结果2和4,是因为ac进行了自动拆箱,因此其比较是基本数据类型的比较,就跟int比较时一样的,“==”在这里比较的是它们的值,而不是引用。 

对于结果1,虽然比较的时候,还是比较对象的reference,但是自动装箱时,java在编译的时候 Integer a = 100; 被翻译成-> Integer a = Integer.valueOf(100); 
关键就在于这个valueOf()的方法。 

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);   
}   
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 = new Integer(i - 128);   
}   
} 

根据上面的jdk源码,java为了提高效率,IntegerCache类中有一个数组缓存了值从-128到127的Integer对象。当我们调用Integer.valueOf(int i)的时候,如果i的值是>=-128且<=127时,会直接从这个缓存中返回一个对象,否则就new一个Integer对象。 

static final Integer cache[] = new Integer[-(-128) + 127 + 1]; //将cache[]变成静态
static {
        for(int i = 0; i < cache.length; i++)
        cache[i] = new Integer(i - 128); //初始化cache[i]
    }

这是用一个for循环对数组cache赋值,cache[255] = new Integer(255-128),也就是newl一个Integer(127) ,并把引用赋值给cache[255],好了,然后是Integer b= 127,流程基本一样,最后又到了cache[255] = new Integer(255-128),这一句,那我们迷糊了,这不是又new了一个对象127吗,然后把引用赋值给cache[255],我们比较这两个引用(前面声明a的时候也有一个),由于是不同的地址,所以肯定不会相等,应该返回false啊!呵呵,这么想你就错了,请注意看for语句给cache[i]初始化的时候外面还一个{}呢,{}前面一个大大的static关键字,是静态的,那么我们就可以回想下static有什么特性了,只能初始化一次,在对象间共享,也就是不同的对象共享同一个static数据。 

那么当我们Integer b = 127的时候,并没有new出一个新对象来,而是共享了a这个对象的引用,记住,他们共享了同一个引用!!!,那么我们进行比较a==b时,由于是同一个对象的引用(她们在堆中的地址相同),那当然返回true了!!! 

 

 [-128,127]之间的数依然会当做基本数据类型进行处理

 

public class Test {
        public static void main(String args[]){
                Integer m = new Integer(5);
                Integer n = new Integer(5);
                System.out.println(m==n);
                m = m-1;
                n = n-1;
                System.out.println(m==n);
        }
}

false
true

原因: 
m,n因为都是new出来的对象,内存地址不一样,所以第一次m==n比较的reference不一样。但是,m=m-1首先进行了自动拆箱m.intValue,相减后再进行装箱动作:m=Integer.valueOf(m.intValue-1),而m和n都在 -128--127的范围,所以自动装箱后,根据上文所述,都是同一个object的引用。因此第二次输出true。 

 

public class TestWhile{ //1
  public static void main(String[] args)//2
  {
  Integer i=0;//3
  Integer j=0;//4
  // Integer i=new Integer(0);//5
  // Integer j=new Integer(0);//6
  while(i<=j & i>=j & i!=j)//7
  { //8
  System.out.println("0000");//9
  }

  }

}

那一行是拆箱?while循环里的条件看似不成立,可为什么可以运行(去掉第5、6行的注释后)? 
解答: 
这种情况下,循环不能运行。 
对于Integer类型,<,<=,>,>=操作将导致拆箱操作,也就是调用Integer的intValue()方法得到相应的基本类型值,然后比较。 
但是,==,!=比较的,是对象的引用(Reference)。 

Integer i = 0;//3 
Integer j = 0;//4 
这两句使用装箱操作,也就是调用Integer.valueOf(int i);注意,不是使用new。 
由于0在 -128--127之间,根据上述,i和j引用的是同一个对象。 
综上,i<=j & i>=j & i!=j中, 
i<=j 和 i>=j都成立,而i!=j不成立,因为i和j引用的是同一个对象。 

故此,循环不会执行。 

注释掉3,4两句,使用5,6两句时,i和j引用的不是同一个对象,所以i!=j成立。i<=j & i>=j & i!=j成立,循环条件总是成立的,while (i <= j & i >= j & i != j)成为无穷循环,不断输出。 

原题: 
循环者的诅咒 
请提供一个对i的声明,将下面的循环转变为一个无限循环:  
while (i <= j && j <= i && i != j) { 


总结,对于要比较Object的值,最稳妥的方法还是调用equals()这个方法,而不是使用==,因此会比较其引用。

 

public boolean equals(Object obj) {
        if (obj instanceof Integer) {
            return value == ((Integer)obj).intValue();
        }
        return false;
}

可以看到,只要两个Integer的intValue是相等的,equals方法总是返回true。

 

参考文章:

1、JAVA的包装类 【转】

2、有关Java包装类及其拆箱装箱的小结

 

posted @ 2016-05-16 15:13  milkty  阅读(431)  评论(0编辑  收藏  举报