Java有8种基本类型,每种基本类型又有对应的包装类型。在Java中,一切都以对象作为基础,但是基本类型并不是对象,如果想以对象的方式使用这8中基本类型,可以将它们转换为对应的包装类型。基本类型和包装类型的对应:
int(4字节) | Integer |
byte(1字节) | Byte |
short(2字节) | Short |
long(8字节) | Long |
float(4字节) | Float |
double(8字节) | Double |
char(2字节) | Character |
boolean(未定) | Boolean |
- //在Java 5之前,只能这样做
- Integer value = new Integer(10);
- //或者这样做
- Integer value = Integer.valueOf(10);
- //直接赋值是错误的
- //Integer value = 10;
在Java 5中,可以直接将整型赋给Integer对象,由编译器来完成从int型到Integer类型的转换,这就叫自动装箱。
- //在Java 5中,直接赋值是合法的,由编译器来完成转换
- Integer value = 10;
- //在Java 5 中可以直接这么做
- Integer value = new Integer(10);
- int i = value;
1 实现
在八种包装类型中,每一种包装类型都提供了两个方法:
静态方法valueOf(基本类型):将给定的基本类型转换成对应的包装类型;
实例方法xxxValue():将具体的包装类型对象转换成基本类型;
下面我们以int和Integer为例,说明Java中自动装箱与自动拆箱的实现机制。看如下代码:
- class Auto //code1
- {
- public static void main(String[] args)
- {
- //自动装箱
- Integer inte = 10;
- //自动拆箱
- int i = inte;
- //再double和Double来验证一下
- Double doub = 12.40;
- double d = doub;
- }
- }
- class Auto//code2
- {
- public static void main(String[] paramArrayOfString)
- {
- Integer localInteger = Integer.valueOf(10);
- int i = localInteger.intValue();
- Double localDouble = Double.valueOf(12.4D);
- double d = localDouble.doubleValue();
- }
- }
实现总结:其实自动装箱和自动封箱是编译器为我们提供的一颗语法糖。在自动装箱时,编译器调用包装类型的valueOf()方法;在自动拆箱时,编译器调用了相应的xxxValue()方法。
2 自动装箱与拆箱中的“坑”
在使用自动装箱与自动拆箱时,要注意一些陷阱,为了避免这些陷阱,我们有必要去看一下各种包装类型的源码。
Integer源码
- public final class Integer extends Number implements Comparable<Integer> {
- private final int value;
- /*Integer的构造方法,接受一个整型参数,Integer对象表示的int值,保存在value中*/
- public Integer(int value) {
- this.value = value;
- }
- /*equals()方法判断的是:所代表的int型的值是否相等*/
- public boolean equals(Object obj) {
- if (obj instanceof Integer) {
- return value == ((Integer)obj).intValue();
- }
- return false;
- }
- /*返回这个Integer对象代表的int值,也就是保存在value中的值*/
- public int intValue() {
- return value;
- }
- /**
- * 首先会判断i是否在[IntegerCache.low,Integer.high]之间
- * 如果是,直接返回Integer.cache中相应的元素
- * 否则,调用构造方法,创建一个新的Integer对象
- */
- public static Integer valueOf(int i) {
- assert IntegerCache.high >= 127;
- if (i >= IntegerCache.low && i <= IntegerCache.high)
- return IntegerCache.cache[i + (-IntegerCache.low)];
- return new Integer(i);
- }
- /**
- * 静态内部类,缓存了从[low,high]对应的Integer对象
- * low -128这个值不会被改变
- * high 默认是127,可以改变,最大不超过:Integer.MAX_VALUE - (-low) -1
- * cache 保存从[low,high]对象的Integer对象
- */
- private static class IntegerCache {
- static final int low = -128;
- static final int high;
- static final Integer cache[];
- static {
- // high value may be configured by property
- int h = 127;
- String integerCacheHighPropValue =
- sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
- if (integerCacheHighPropValue != null) {
- int i = parseInt(integerCacheHighPropValue);
- i = Math.max(i, 127);
- // Maximum array size is Integer.MAX_VALUE
- h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
- }
- 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() {}
- }
- }
以上是Oracle(Sun)公司JDK 1.7中Integer源码的一部分,通过分析上面的代码,得到:
1)Integer有一个实例域value,它保存了这个Integer所代表的int型的值,且它是final的,也就是说这个Integer对象一经构造完成,它所代表的值就不能再被改变。
2)Integer重写了equals()方法,它通过比较两个Integer对象的value,来判断是否相等。
3)重点是静态内部类IntegerCache,通过类名就可以发现:它是用来缓存数据的。它有一个数组,里面保存的是连续的Integer对象。
(a) low:代表缓存数据中最小的值,固定是-128。
(b) high:代表缓存数据中最大的值,它可以被该改变,默认是127。high最小是127,最大是Integer.MAX_VALUE-(-low)-1,如果high超过了这个值,那么cache[ ]的长度就超过Integer.MAX_VALUE了,也就溢出了。
(c) cache[]:里面保存着从[low,high]所对应的Integer对象,长度是high-low+1(因为有元素0,所以要加1)。
4)调用valueOf(int i)方法时,首先判断i是否在[low,high]之间,如果是,则复用Integer.cache[i-low]。比如,如果Integer.valueOf(3),直接返回Integer.cache[131];如果i不在这个范围,则调用构造方法,构造出一个新的Integer对象。
5)调用intValue(),直接返回value的值。
通过3)和4)可以发现,默认情况下,在使用自动装箱时,VM会复用[-128,127]之间的Integer对象。
- Integer a1 = 1;
- Integer a2 = 1;
- Integer a3 = new Integer(1);
- //会打印true,因为a1和a2是同一个对象,都是Integer.cache[129]
- System.out.println(a1 == a2);
- //false,a3构造了一个新的对象,不同于a1,a2
- System.out.println(a1 == a3);
Byte源码
- public final class Byte extends Number implements Comparable<Byte> {
- //Byte表示的范围是[-128,127]
- public static final byte MIN_VALUE = -128;
- public static final byte MAX_VALUE = 127;
- private final byte value;
- public Byte(byte value) {
- this.value = value;
- }
- /**
- * 缓存Byte对象
- * 将Byte可能的256个对象全部保存到cache[]中
- * @author cxy
- *
- */
- private static class ByteCache {
- private ByteCache(){}
- static final Byte cache[] = new Byte[-(-128) + 127 + 1];
- static {
- for(int i = 0; i < cache.length; i++)
- cache[i] = new Byte((byte)(i - 128));
- }
- }
- /*直接返回ByteCache.cache[]中相应的对象*/
- public static Byte valueOf(byte b) {
- final int offset = 128;
- return ByteCache.cache[(int)b + offset];
- }
- /*返回此对象的byte值*/
- public byte byteValue() {
- return value;
- }
- }
同样的Character中的CharacterCache类也有一个cache[ ],缓存了[0,127]中的元素。Short和Integer一样,缓存了[-128,127]之间的数,不同的是,Integer可以修改high的值,ShortCache中则是写死的,不能改变。Long的实现方法和Short一样。
Double和Float
- /*Double.valueOf(double d)*/
- public static Double valueOf(Double d) {
- return new Double(d);
- }
- /*Float.valueOf(float f)*/
- public static Float valueOf(float f) {
- return new Float(f);
- }
Boolean源码
- public final class Boolean implements java.io.Serializable,Comparable<Boolean>
- {
- /*boolean只有两种取值:true,false,所以不需要内部类来缓存了*/
- public static final Boolean TRUE = new Boolean(true);
- public static final Boolean FALSE = new Boolean(false);
- private final boolean value;
- public Boolean(boolean value) {
- this.value = value;
- }
- public boolean booleanValue() {
- return value;
- }
- /**
- * 根据b的值,返回对应的对象
- */
- public static Boolean valueOf(boolean b) {
- return (b ? TRUE : FALSE);
- }
- }
- boolean b = true;
- Boolean b1 = b;
- boolean b2 = b1;
Boolean还是用到了缓存,由于boolean只有两种取值,所以没有必要使用内部类或者数组来保存缓存的对象,直接定义两个静态属性即可,也就是Boolean.TRUE和Boolean.FALSE。在调用Boolean.valueOf(boolean b)是,返回的是缓存的TRUE或者FALSE,代码验证:
- Boolean b1 = Boolean.valueOf(true);
- Boolean b2 = Boolean.valueOf(true);
- Boolean b3 = new Boolean(true);
- //true,因为返回的都是TRUE对象
- System.out.println(b1 == b2);
- //false,因为b1是TRUE,b3则是一个新的Boolean对象
- System.out.println(b1 == b3);
发生时机
来欣赏一个比较典型的例子:
- public class AutoWrapperTrap {
- public static void main(String[] args) {
- //[-128,127]之间,自动装箱会复用对象
- Integer a = 1;
- Integer b = 2;
- Integer c = 3;
- Integer d = 3;
- //不会复用
- Integer e = 321;
- Integer f = 321;
- int base = 3;
- Long g = 3L;
- System.out.println(c == base);//true c自动拆箱
- System.out.println(c == d);//true
- System.out.println(e == f);//false
- System.out.println(c == (a + b));//true 遇到算术运算,自动拆箱
- System.out.println(c.equals(a + b));//true 需要对象,自动装箱
- System.out.println(g == (a + b));//true
- System.out.println(g.equals(a + b));//false 只会自动装箱为对应的包装类型
- }
- }
- public class AutoWrapperTrap
- {
- public static void main(String[] args)
- {
- Integer a = Integer.valueOf(1);
- Integer b = Integer.valueOf(2);
- Integer c = Integer.valueOf(3);
- Integer d = Integer.valueOf(3);
- Integer e = Integer.valueOf(321);
- Integer f = Integer.valueOf(321);
- int base = 3;
- Long g = Long.valueOf(3L);
- System.out.println(c.intValue() == base);
- System.out.println(c == d);
- System.out.println(e == f);
- System.out.println(c.intValue() == a.intValue() + b.intValue());
- System.out.println(c.equals(Integer.valueOf(a.intValue() + b.intValue())));
- System.out.println(g.longValue() == a.intValue() + b.intValue());
- System.out.println(g.equals(Integer.valueOf(a.intValue() + b.intValue())));
- }
- }
(1)当需要一个对象的时候会自动装箱,比如Integer a = 10;equals(Object o)方法的参数是Object对象,所以需要装箱。
(2)当需要一个基本类型时会自动拆箱,比如int a = new Integer(10);算术运算是在基本类型间进行的,所以当遇到算术运算时会自动拆箱,比如代码中的 c == (a + b);
(3) 包装类型 == 基本类型时,包装类型自动拆箱;
需要注意的是:“==”在没遇到算术运算时,不会自动拆箱;基本类型只会自动装箱为对应的包装类型,代码中最后一条说明的内容。
总结
在JDK 1.5中提供了自动装箱与自动拆箱,这其实是Java 编译器的语法糖,编译器通过调用包装类型的valueOf()方法实现自动装箱,调用xxxValue()方法自动拆箱。自动装箱和拆箱会有一些陷阱,那就是包装类型复用了某些对象。
(1)Integer默认复用了[-128,127]这些对象,其中高位置可以修改;
(2)Byte复用了全部256个对象[-128,127];
(3)Short服用了[-128,127]这些对象;
(4)Long服用了[-128,127];
(5)Character复用了[0,127],Charater不能表示负数;
Double和Float是连续不可数的,所以没法复用对象,也就不存在自动装箱复用陷阱。
Boolean没有自动装箱与拆箱,它也复用了Boolean.TRUE和Boolean.FALSE,通过Boolean.valueOf(boolean b)返回的Blooean对象要么是TRUE,要么是FALSE,这点也要注意。
本文介绍了“真实的”自动装箱与拆箱,为了避免写出错误的代码,又从包装类型的源码入手,指出了各种包装类型在自动装箱和拆箱时存在的陷阱,同时指出了自动装箱与拆箱发生的时机。
转载请注明出处:喻红叶《Java的装箱与拆箱机制》