Java的装箱与拆箱机制

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增加了自动装箱与自动拆箱机制,方便基本类型与包装类型的相互转换操作。在Java 5之前,如果要将一个int型的值转换成对应的包装器类型Integer,必须显式的使用new创建一个新的Integer对象,或者调用静态方法Integer.valueOf()。

//在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;
自动装箱与自动拆箱为程序员提供了很大的方便,而在实际的应用中,自动装箱与拆箱也是使用最广泛的特性之一。自动装箱和自动拆箱其实是Java编译器提供的一颗语法糖(语法糖是指在计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。通过可提高开发效率,增加代码可读性,增加代码的安全性)。

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;
		
	}
}
上面的代码先将int型转为Integer对象,再讲Integer对象转换为int型,毫无疑问,这是可以正确运行的。可是,这种转换是怎么进行的呢?使用反编译工具,将生成的Class文件在反编译为Java文件,让我们看看发生了什么:
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();
  }
}
我们可以看到经过javac编译之后,code1的代码被转换成了code2,实际运行时,虚拟机运行的就是code2的代码。也就是说,虚拟机根本不知道有自动拆箱和自动装箱这回事;在将Java源文件编译为class文件的过程中,javac编译器在自动装箱的时候,调用了Integer.valueOf()方法,在自动拆箱时,又调用了intValue()方法。我们可以看到,double和Double也是如此。

实现总结:其实自动装箱和自动封箱是编译器为我们提供的一颗语法糖。在自动装箱时,编译器调用包装类型的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;
	    }
	}
byte的表示范围是[-128,127],在Byte内部同样有一个ByteCache类,它也同样有一个cache[ ],它里面保存了所有可能的256个Byte对象。所以在自动装箱时,所有的Byte对象都是复用ByteCache.cache[ ]中的元素。

同样的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);
    }
从源码中可以看出,Double和Float都没有缓存了,调用valueOf()方法,直接构造出一个新的对象。Double和Float之所以不用缓存,是因为没有办法缓存,(0,1)这么小的一个区间里面,就有无数个double或float数,根本无从缓存。所以在使用Double和Float自动装箱时,全都是构造新的对象,没有缓存。

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的源码,发现Boolean没有无参的valueOf(),我们可以推断Boolean没有自动装箱与封箱,可以通过代码验证一下:
 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的装箱与拆箱机制》


  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值