什么是自动拆装箱
-
装箱:将基本数据结构转化为包装器类型
Integer i = 100;
-
拆箱:将包装器类型转变为基本数据类型
int ii = i;
基本数据类型的自动装箱、拆箱,是自J2SE 5.0开始提供的功能。打包数据类型虽然提供了方便,但是方便的同时表示隐藏了细节。
J2SE 5.0之前,包装基本数据类型,需要使用生成新的
Integer integer = new Integer(10);
J2SE 5.0之后,可以自动装箱:
Integer integer = 10;
需要拆装箱类型
基本数据类型与封装类型对应如下:
自动拆装箱基本实现
-
装箱:用包装器类型的valueOf()方法实现。
下面为Integer的valueOf()方法的代码:
public static Integer valueOf(int i) { // 判断i的大小,如果在[-128, 127]之间,则返回已在缓存中的Integer对象,否则 if(i >= -128 && i <= IntegerCache.high) return IntegerCache.cache[i + 128]; else return new Integer(i); } private static class IntegerCache { static final int high; static final Integer cache[]; static { final int low = -128; // high value may be configured by property int h = 127; if (integerCacheHighPropValue != null) { // Use Long.decode here to avoid invoking methods that // require Integer's autoboxing cache to be initialized int i = Long.decode(integerCacheHighPropValue).intValue(); i = Math.max(i, 127); // Maximum array size is Integer.MAX_VALUE h = Math.min(i, Integer.MAX_VALUE - -low); } high = h; cache = new Integer[(high - low) + 1]; int j = low; for(int k = 0; k < cache.length; k++) cache[k] = new Integer(j++); } private IntegerCache() {} }
- valueOf()方法最终返回一个Integer对象。
- 装箱的过程会创建对应的对象,消耗内存,影响性能。
-
拆箱:用包装器的xxxValue()方法实现(其中xxx则是基本数据类型)
基本数据类型和包装器内存中的区别,如下图
基本数据类型是保存在栈中,但包装器类型的值则是保存在对内存中,对象保存在栈中,并指向其值。
自动拆装箱的好处
对现实世界的模拟
例如,表示年龄,当缺失此信息时,用null表示明显好于0,此使就需要使用包装器类型。
支持泛型
泛型必须是一个对象
提供丰富的api和属性
自动封装小问题
-
注意编译造成的运行时错误
如下代码:
Integer i = null; int j = i;
上面代码编译时没有问题,但是运行时会有NullPointerException错误。因为编译器会将代码编译为一下语句:
Integer i = null; int j = i.intValue();
null表示没有任何对象实体,没有方法可以操作,因此抛出异常。
-
自动装箱时取值范围问题
由于自动装箱时,调用包装类的valueOf()方法,有固定的取值范围,当使用==判断时,会导致引用地址是否相同的问题。
public class Main { public static void main(String[] args) { Integer i1 = 100; Integer i2 = 100; Integer i3 = 200; Integer i4 = 200; System.out.println(i1==i2); //true System.out.println(i3==i4); //false } }
上面代码便是个明显的例子:
i1 == i2时,由于两个值是100,在[-128, 127]区间,valueOf()会在缓冲区寻找最大值,如果有,则会返回已存在的地址,因此是一样的;
而 i3 == i4,值为200, 大于127,将直接创建新的Integer对象,因此两个地址是不一样的。
但不是所有的包装类都是如此,在某个范围内的整型数值的个数是有限的,而浮点数却不是,因此浮点数每次封箱都是重新创建新对象。
整型取值范围如下图:
此时,Boolean需要拿出来讨论,我们先看看Boolean的表现:
public class Main { public static void main(String[] args) { Boolean i1 = false; Boolean i2 = false; Boolean i3 = true; Boolean i4 = true; System.out.println(i1==i2);//true System.out.println(i3==i4);//true } }
此使,两种情况都是true,这是为什么呢?我们先看看Boolean的valueOf()方法:
public static final Boolean FALSE = new Boolean(false); public static final Boolean TRUE = new Boolean(true); public static Boolean valueOf(boolean b) { return (b ? TRUE : FALSE); }
可以看见,Boolean已经提前创建好了两个对象,这样可以避免重复创建太多对象,因为它只有两个值。所以,Boolean封箱的时候都是同一个对象,所以地址都是相同的。
PS:需要注意上面的情况是居于调用valueOf()方法的自动装箱,如果显式装箱则没有此问题
Integer i9 = new Integer(100); Integer j9 = new Integer(100); System.out.println(i9 == j9); //false
-
包装类对象和基本数据类型对比
-
==
Integer num1 = 400; int num2 = 400; System.out.println(num1 == num2); //true
此时对包装类进行了拆箱,因此两者相同。
对于
==
比较:- 如果两边都是同一包装器类型的引用,则比较是否指向同一个对象;
- 如果有一边是算术表达式,则比较的是数值(包括类型不同),因为计算过程会出发自动拆箱。
-
equals()方法
Integer num1 = 100; int num2 = 100; Long num3 = 200l; System.out.println(num1 + num2); //200 System.out.println(num3 == (num1 + num2)); //true System.out.println(num3.equals(num1 + num2)); //false
为什么会出现上面的情况呢,我们先来看看Long的equals()方法的代码:
public boolean equals(Object obj) { if (obj instanceof Long) { return value == ((Long)obj).longValue(); } return false; }
从上面代码可以看出,equals()方法是需要先比较类型,再比较数值的。上面说了,计算表达式会进行拆箱,而equals()方法的参数为包装类,所以需要先进行封装,再比较。
-
-
Integer i = new Integer(xxx)和Integer i =xxx;这两种方式的区别
- 第一种方法不会自动封装,因此不存在valueOf()的过程
- 第二种方式的执行效率和资源占用由于第一种,但并非绝对。
参考文章来源:
完结,撒花~