zoukankan      html  css  js  c++  java
  • 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,这点也要注意。

    本文介绍了“真实的”自动装箱与拆箱,为了避免写出错误的代码,又从包装类型的源码入手,指出了各种包装类型在自动装箱和拆箱时存在的陷阱,同时指出了自动装箱与拆箱发生的时机。
    ————————————————
    版权声明:本文为CSDN博主「喻红叶」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/yuhongye111/java/article/details/31850779

  • 相关阅读:
    JAVA--导数到Mongodb
    关于jquery的事件委托-bind,live,delegate,on的区别发展
    cookies localStorage和sessionStorage的区别
    px em 和rem之间的区别
    js中string常用方法
    js中number常用方法
    json格式常用操作
    Node.js到底是做什么的?这是我看到最好的解释了
    数组常用操作方法
    JQuery.Ajax()的data参数类型
  • 原文地址:https://www.cnblogs.com/DengGao/p/12825988.html
Copyright © 2011-2022 走看看