什么是自动装箱和拆箱?
在Java1.5下进行过编程的话,你一定不会陌生这一点,你不能直接地向集合(Collections)中放入原始类型值,因为集合只接收对象。通常这种情况下你的做法是,将这些原始类型的值转换成对象,然后将这些转换的对象放入集合中。使用Integer,Double,Boolean等这些类我们可以将原始类型值转换成对应的对象,但是从某些程度可能使得代码不是那么简洁精炼。为了让代码简练,Java 1.5引入了具有在原始类型和对象类型自动转换的装箱和拆箱机制。但是自动装箱和拆箱并非完美,在使用时需要有一些注意事项,如果没有搞明白自动装箱和拆箱,可能会引起难以察觉的bug。
原始类型byte,short,char,int,long,float,double和boolean对应的封装类为Byte,Short,Character,Integer,Long,Float,Double,Boolean。
自动装箱
把基本类型用它们对应的引用类型封装起来,使它们具有对象的特质,可以调用toString()、hashCode()、getClass()、equals()等方法。
如下:
Integer a=3;//这是自动装箱
其实编译器调用的是static Integer valueOf(int i)这个方法,valueOf(int i)返回一个表示指定int值的Integer对象,那么就变成这样:
Integer a=3; => Integer a=Integer.valueOf(3);
自动拆箱
跟自动装箱的方向相反,将Integer及Double这样的引用类型的对象重新简化为基本类型的数据。
如下:
int i = new Integer(2);//这是拆箱
编译器内部会调用int intValue()返回该Integer对象的int值
注意:自动装箱和拆箱是由编译器来完成的,编译器会在编译期根据语法决定是否进行装箱和拆箱动作。
自动装箱和拆箱的要点
自动装箱时编译器调用valueOf将原始类型值转换成对象,同时自动拆箱时,编译器通过调用类似intValue(),doubleValue()这类的方法将对象转换成原始类型值。
自动装箱是将boolean值转换成Boolean对象,byte值转换成Byte对象,char转换成Character对象,float值转换成Float对象,int转换成Integer,long转换成Long,short转换成Short,自动拆箱则是相反的操作。
自动装箱和拆箱的实例
自动装箱主要发生在两种情况下,一种是在赋值时,还有一种是在调用方法时。
赋值时
//before autoboxing
Integer iObject = Integer.valueOf(3);
Int iPrimitive = iObject.intValue()
//after java5
Integer iObject = 3; //autobxing - primitive to wrapper conversion
int iPrimitive = iObject; //unboxing - object to primitive conversion
方法调用时
public static Integer show(Integer iParam){
System.out.println("autoboxing example - method invocation i: " + iParam);
return iParam;
}
//autoboxing and unboxing in method invocation
show(3); //autoboxing
int result = show(3);
//unboxing because return type of method is Integer
自动装箱的作用和自动拆箱的作用
自动装箱和自动拆箱是简化了基本数据类型和相对应对象的转化步骤,节省了常用数值的内存开销和创建对象的开销,提高了效率。
源码分析
ValueOf
public static Integer valueOf(inti) {
if(i >= -128 &&i <=IntegerCache.high)
//如果i在-128~high之间,就直接在缓存中取出i的Integer类型对象
return IntegerCache.cache[i + 128];
else
return new Integer(i); //否则就在堆内存中创建
}
IntegerCache内部类
private static class IntegerCache {
//内部类,注意它的属性都是定义为static final
static final inthigh; //缓存上界
static final Integer cache[];//cache缓存是一个存放Integer类型的数组
static {//静态语句块
final int low = -128;//缓存下界,值不可变
// high value may beconfigured by property
int h = 127;// h值,可以通过设置jdkAutoBoxCacheMax参数调整(参见(3))
if (integerCacheHighPropValue !=null) {
// Use Long.decode here to avoid invoking methods that
// require Integer's autoboxing cache to be initialized
// 通过解码integerCacheHighPropValue,而得到一个候选的上界值
int i = Long.decode(integerCacheHighPropValue).intValue();
// 取较大的作为上界,但又不能大于Integer的边界MAX_VALUE
i = Math.max(i, 127);//上界最小为127
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - -low);
}
high = h; //上界确定,此时high默认一般是127
// 创建缓存块,注意缓存数组大小
cache =new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k <cache.length; k++)// -128到high值逐一分配到缓存数组
}
private IntegerCache() {}//构造方法,不需要构造什么
}
通过IntegerCacheHighPropValue变量设置自动装箱池的大小
/**
* Cache to support theobject identity semantics of autoboxing for values between
* -128 and 127(inclusive) as required by JLS.
*
* The cache isinitialized on first usage. During VM initialization the
* getAndRemoveCachePropertiesmethod may be used to get and remove any system
* properites thatconfigure the cache size. At this time, the size of the
* cache may be controlledby the vm option -XX:AutoBoxCacheMax=<size>.
*/
对象相等比较
这是一个比较容易出错的地方,”==“可以用于原始值进行比较,也可以用于对象进行比较,当用于对象与对象之间比较时,比较的不是对象代表的值,而是检查两个对象是否是同一对象,这个比较过程中没有自动装箱发生。进行对象值比较不应该使用”==“,而应该使用对象对应的equals方法。看一个能说明这个问题的例子。
public class AutoboxingTest {
public static void main(String args[]) {
// Example 1: == comparison pure primitive – no autoboxing
int i1 = 1;
int i2 = 1;
System.out.println("i1==i2 : " + (i1 == i2)); // true
// Example 2: equality operator mixing object and primitive
Integer num1 = 1; // autoboxing
int num2 = 1;
System.out.println("num1 == num2 : " + (num1 == num2)); // true
// Example 3: special case - arises due to autoboxing in Java
Integer obj1 = 1; // autoboxing will call Integer.valueOf()
Integer obj2 = 1; // same call to Integer.valueOf() will return same
// cached Object
System.out.println("obj1 == obj2 : " + (obj1 == obj2)); // true
// Example 4: equality operator - pure object comparison
Integer one = new Integer(1); // no autoboxing
Integer anotherOne = new Integer(1);
System.out.println("one == anotherOne : " + (one == anotherOne)); // false
}
}
Output:
i1==i2 : true
num1 == num2 : true
obj1 == obj2 : true
one == anotherOne : false
值得注意的是当值比较。obj1和obj2的初始化都发生了自动装箱操作。但是处于节省内存的考虑,JVM会缓存-128到127的Integer对象。因为obj1和obj2实际上是同一个对象。所以使用”==“比较返回true。
容易混淆的对象和原始值
混乱使用对象和原始数据值,一个具体的例子就是当我们在一个原始数据值与一个对象进行比较时,如果这个对象没有进行初始化或者为Null,在自动拆箱过程中obj.xxxValue,会抛出NullPointerException
如下面的代码
private static Integer count;
//NullPointerException on unboxing
if( count <= 0){
System.out.println("Count is not started yet");
}
缓存的对象
在Java中,会对-128到127的Integer对象进行缓存,当创建新的Integer对象时,如果符合这个这个范围,并且已有存在的相同值的对象,则返回这个对象,否则创建新的Integer对象。
在Java中另一个节省内存的例子就是字符串常量池
总结
Byte,Short,Long对应的是-128~127
Character对应的是0~127
Float和Double没有自动装箱池
(1)Integer和 int之间可以进行各种比较;Integer对象将自动拆箱后与int值比较
(2)两个Integer对象之间也可以用>、<等符号比较大小;两个Integer对象都拆箱后,再比较大小
(3) 两个Integer对象最好不要用==比较。因为:-128~127范围(一般是这个范围)内是取缓存内对象用,所以相等,该范围外是两个不同对象引用比较,所以不等。