前言:自动装箱和拆箱是JDK1.5提供的新特性,大致是由编译器自动帮助完成基本类型和基本类型的包装类之间的转换。
好吧,知道了自动装箱的大致意思之后,我们先来复习一个有关基本类型以及包装类的概念:
基本类型:int,double,float,long,boolean,char,btye,short
基本类型包装类:Integer,Double,Float,Long,Boolean,Char,Byte,Short
两者有什么差别,包装类是类,是对象,而基本类型是有值的“变量”,再细化一点,就是包装类的实例(对象)创建在堆上,而基本类型创建在栈上,这是JAVA为了程序的速度考虑做出的优化,那么既然是创建在栈上,所以基本类型都有大小的规定(每种基本类型所占的字节是一定的)。
扯句题外话,那么为什么java的设计者要设计出包装类呢?我的理解是既然java作为一门面向对象的语言,那么包装类作为类,可以容纳更多的信息,可以提供更多的操作。另外,包装类都实现了Comparable接口,可以实现对象之间的比较,所以包装类之间的比较尽量用compareTo,而不是><=这些运算符。切记。
在JDK1.5之前,我们要实现基本类型和包装类之间的转换,大多是通过包装类提供的方法,Integer i = Integer.valueOf(5)或者int j = i.intValue()来做互相转换的。JDK1.5之后,编译器会在我们做赋值操作(这里所说的赋值操作不包括构造函数,我们后面会讲)的时候帮我们自动完成基本类型和包装类之间的相互转换。
上面我们所说的赋值操作,基本上可以分为两种情况,一种是显式赋值,另一种是隐式赋值:
显式赋值,我们可以理解为有赋值符号出现的情况,比如,Integer i = 11;这里编译器会自动的帮你完成把11这个int的基本类型装箱成包装类实例的工作,这样就不用我们再手动的转换了。
隐式赋值,就是没有复制符号出现的情况,这点我们很多人经常忽略,比如方法调用时候的参数传递,比如我们有一个接受Integer类型参数的方法(void method(Integer i)),我们在调用的时候可以直接传递一个基本类型的实参进去(method(5)),这时候编译器会自动的将我们传递进去的实参装箱成包装类,从而完成对方法的调用。很简单的一个过程,里面的原理却不简单,这是因为,方法调用的时候,在内存中的栈上,首先会为方法的形参创建内存区域,然后将我们传递进去的实参赋值给形参,看到没,就是在这时,发生了赋值操作,才会让编译器完成装箱操作。当然,方法执行完后,栈上有关形参的内存区域会被回收。
还有我们要记住一点,就是编译器的装箱/拆箱原则,就是基本类型可以先加宽(比如int转换为long),再转变成宽类型的包装类型(Long),但是不能直接转成宽类型的包装类型。
比如我们有个方法 void method(long l),我们通过method(5)是可以调用该方法的,因为int类型的5被加宽成了long类型;但是如果这个方法变成void method(Long l),我们通过method(5)就不能调用它了,因为int类型的5是不能直接转换成Long包装类的。切记。
最后还有一个值得注意的地方,就是上面我们提到的显示赋值的情况下,比如Integer i = 11的情况,实际上在源码中是调用到了Integer的静态方法valueOf(),在这一块,java的设计者采用了cache pool的设计模式,在Integer这个类里面有一个长度为256的静态Integer数组,里面存储了从-128到127的Integer对象,当我们传递进去的参数在这个范围之内时,这个方法会返回该数据中实现保存的Integer对象实例,只有超过该范围时,才会返回new出来的Integer对象实例。
所以会出现下面代码的情况:
PS:==符号比较的基本类型的值,对对象类型而言,比较的是对象的内存地址
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
装箱:形如--->Integer num1 = 2;
一般我们要创建一个类的对象的时候,我们会这样:
Class a = newClass(parameter);
当我们创建一个Integer对象时,却可以这样:
Integer i = 100; (注意:不是 int i = 100; )
实际上,执行上面那句代码的时候,系统为我们执行了:Integer i = new Integer(100); 此即基本数据类型的自动装箱功能。
拆箱:形如--->System.out.println(num1+ 3);也就是将对象中的基本数据从对象中自动取出
基本数据类型对象缓存:
Integernum1 = 12;
Integernum2 = 12;
System.out.println(num1== num2); true
Integernum3 = 129;
Integernum4 = 129;
System.out.println(num3== num4); false (-128~127)
Integernum5 = Integer.valueOf(12);
Integernum6 = Integer.valueOf(12);
System.out.println(num5== num6); true
Integernum7 = Integer.valueOf(129);
Integernum8 = Integer.valueOf(129);
System.out.println(num7== num8); false
注意:
equals() 比较的是两个对象的值(内容)是否相同。
"==" 比较的是两个对象的引用(内存地址)是否相同,也用来比较两个基本数据类型的变量值是否相等。
在自动装箱时对于值从–128到127之间的值,它们被装箱为Integer对象后,会存在内存中被重用,
所以范例中,num1 与 num2实际上参考至同一个对象。
如果超过了从–128到127之间的值,被装箱后的Integer对象并不会被重用,
即相当于每次装箱时都新建一个 Integer对象,所以范例中,num7与num8参考的是不同的对象。
另外,当不使用自动装箱功能的时候,情况与普通类对象一样
例如String的自动拆装箱
String str1 = "abc";
String str2 = "abc";
System.out.println(str2==str1); //输出为 true
System.out.println(str2.equals(str1)); //输出为 true
String str3 = new String("abc");
String str4 = new String("abc");
System.out.println(str3==str4); //输出为 false
System.out.println(str3.equals(str4)); //输出为 true
例子二:
String d ="2";
String e = "23";
e = e.substring(0, 1);
System.out.println(e.equals(d)); //输出为true
System.out.println(e==d); //输出为 false
充分说明了:
equals() 比较的是两个对象的值(内容)是否相同。
"==" 比较的是两个对象的引用(内存地址)是否相同
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
自动装包/拆包大大方便了基本类型数据和它们包装类地使用。
自动装包:基本类型自动转为包装类.(int >> Integer)
自动拆包:包装类自动转为基本类型.(Integer >> int)
在JDK1.5之前,我们总是对集合不能存放基本类型而耿耿于怀,现在自动转换机制解决了我们的问题。
int a = 3;
Collection c = new ArrayList();
c.add(a);//自动转换成Integer.
Integer b = new Integer(2);
c.add(b + 2);
这里Integer先自动转换为int进行加法运算,然后int再次转换为Integer.