Java自动拆箱和装箱

参考文章深入剖析Java中的装箱和拆箱

一、什么是装箱/拆箱

在讲之前,得先提一下为什么两个概念:基本数据类型及其包装类,我们都知道Java是一种面向对象的语言,但是Java中的基本数据类型是不面向对象的,这时在使用中便会存在诸多的不便,为了解决这个不足,在设计类时为每个基本数据类型设计了一个对应的包装类(Wrapper Class),他们之间的对应关系如下表:

基本类型 包装类
boolean Boolean
char Character
byte Byte
short Short
int Integer
long Long
float Float
double Double
void Void

弄清楚了这个概念之后,我们回到正题,在Java SE5之前,如果要生成一个数值为10的Integer对象,只需要这样就可以了:

Integer i = new Integer(10);

而从Java SE5开始就提供了自动装箱的特性,如果生成一个数值为10的Integer对象,只需这样就可以了:

Integer i = 10;

这个过程中会自动根据数值创建对应的Integer对象,这就是装箱。那么什么是拆箱呢,顾名思义,跟装箱对应,就是自动将包装器类型转换为基本数据类型:

Integer i = 10 //装箱
int n = i; //拆箱

简单一点说,装箱就是自动将基本数据类型转换为包装器类型拆箱就是自动将包装器类型转换为基本数据类型

二、拆箱与装箱是如何实现的?

以下面这段代码为例:

public class Main {
    public static void main(String[] args){
        Integer i = 10;
        int n = i;
    }
}

利用IDEA对其进行反编译:


从我圈出来的地方可以看出,在装箱的时候调用的是IntegervalueOf(int)方法。而在拆箱的时候自动调用的是IntegerintValue方法

其他的也类似,如DoubleCharacter,在这里就不展示截图了。

由此可得出结论
装箱过程是通过调用包装器的valueOf方法实现的,二拆箱是通过调用包装器的xxxValue方法实现的。(xxx代表对应的基本数据类型)

三、总结一些常见的面试题

虽然到这里你已经明白的装箱和拆箱的概念,但是如果碰到了相关的考题却不一定能答得上来,下面就来列举一些常见的相关考题:

  1. 下面这段代码的输出结果是什么
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);
        System.out.println(i3==i4);
    }
}

先直接上结果吧:


分析
首先,在创建Integer对象的时候,设计到一个自动装箱的过程,从上面的分析我们得知这其中涉及到IntegervalueOf方法,那么我们就从这里入手:

public static Integer valueOf(int i) {
      if (i >= IntegerCache.low && i <= IntegerCache.high)
          return IntegerCache.cache[i + (-IntegerCache.low)];
      return new Integer(i);
}

//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 =
                VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
        if (integerCacheHighPropValue != null) {
            try {
                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);
            } catch( NumberFormatException nfe) {
                // If the property cannot be parsed into an int, ignore it.
            }
        }
        high = h;

        cache = new Integer[(high - low) + 1];
        int j = low;
        for(int k = 0; k < cache.length; k++)
            cache[k] = new Integer(j++);

        // range [-128, 127] must be interned (JLS7 5.1.7)
        assert Integer.IntegerCache.high >= 127;
    }

    private IntegerCache() {}
}

从源码中我们可以得知,在通过valueOf方法创建Integer对象的时候,如果数值在[-128,127]之间,便返回指向IntegerCache.cache中已经存在的对象的引用,否则创建一个新的Integer对象

所以上面例子中i1i2指向的是同一个对象,而i3i4则分别指向不同的对象。

  1. 再来看看下面找个例子
public class Main {
    public static void main(String[] args){
        Double i1 = 100.0;
        Double i2 = 100.0;
        Double i3 = 200.0;
        Double i4 = 200.0;

        System.out.println(i1 == i2);
        System.out.println(i3 == i4);
    }
}

是不是在觉得这跟上面那个例子一样一样的?先来看看答案:


why?先来看看DoublevalueOf是怎么实现的:

public static Double valueOf(double d) {
    return new Double(d);
}

这下明白结果是怎么来的了吧?还不懂?其实很简单,整数100101是连续的,但是对于浮点数,100.1100.2之间有多少个浮点数?无数个!所以这里无法像Integer那样做一个缓存。

到了这里,已经明白了原理,就可以做一个总结了:

  • IntegerShortByteCharacterLong这几个类的valueOf方法的实现是类似的DoubleFloatvalueOf方法的实现是类似的。
  • xxxCache存在的意义:缓存缓存,当然是提高效率,避免多次重复创建相同对象了!(当然可能还有其他原因只是我不知道罢了 = =)
  1. 继续上例子:
public class Main {
    public static void main(String[] args) {
        Boolean b1 = false;
        Boolean b2 = false;
        Boolean b3 = true;
        Boolean b4 = true;

        System.out.println(b1 == b2);
        System.out.println(b3 == b4);
    }
}

猛地发现前面只总结7种数据类型,还有最优一种。老规矩先上答案:


源码:

public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);

public static Boolean valueOf(boolean b) {
     return (b ? TRUE : FALSE);
}

ok,我们可以看到Boolean内部定义了两个静态常量,可以理解成提前做了缓存吧,只不过bool类型只有truefalse,所以没必要像Integer那样做一个cache了。

  1. Integer i = new Integer(xxx)Integer i = xxx两种方式的区别
    这个题目可以从多个角度切入,但是自动装/拆箱的要点一定要答上,例如:

    1. 第一种方式不会触发自动装箱的过程;而第二种方式会触发;
    2. 在执行效率和资源占用上的区别。第二种方式的执行效率和资源占用在一般情况下要优于第一种情况(注意这并不是绝对的)。
  2. 最后再来一个大练习:

public static void main(String[] args) {

    Integer a = 1;
    Integer b = 2;
    Integer c = 3;
    Integer d = 3;
    Integer e = 321;
    Integer f = 321;
    Long g = 3L;
    Long h = 2L;

    
    System.out.println(c == d);
    System.out.println(e == f);
    System.out.println(c == (a + b));
    System.out.println(c.equals(a + b));
    System.out.println(g == (a + b));
    System.out.println(g.equals(a + b));
    System.out.println(g.equals(a + h));
}

虽然上面分别讲了下这些包装类的valueOf方法的实现,但是这里可能还是会犯迷糊,下面先提供两点提示:

  • ==运算符的两个操作数都是包装类的引用时比较的是它们是否指向同一个引用,而如果其中有一个操作数是表达式(包含算术运算)则比较的是数值(即触发自动拆箱的过程)。
  • 对于包装类,equals方法并不会进行类型转换
    下面是运行结果:

    分析:
    首先前两个结果相信没有什么异议。第三个由于a+b是一个算术运算,因此会触发自动拆箱,最终比较的是数值,所以结果为trueg == (a + b)同理。
    再来说下第四个,首先a+b最终得到的是一个Integer类型的值,然后再执行equals方法,后面两个同理。

四、总结

  • 装箱就是自动将基本数据类型转换为包装器类型;拆箱就是自动将包装器类型转换为基本数据类型。
  • 装箱过程是通过调用包装器的valueOf方法实现的,二拆箱是通过调用包装器的xxxValue方法实现的。(xxx代表对应的基本数据类型)
  • IntegerShortByteCharacterLong这几个类的valueOf方法的实现是类似的DoubleFloatvalueOf方法的实现是类似的。Boolean定义了两个静态常量当做缓存。
  • xxxCache存在的意义:提高效率,避免多次重复创建相同对象了。
  • ==运算符的两个操作数都是包装类的引用时比较的是它们是否指向同一个引用,而如果其中有一个操作数是表达式(包含算术运算)则比较的是数值(即触发自动拆箱的过程)。
  • 对于包装类,equals方法并不会进行类型转换
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,402评论 6 499
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,377评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,483评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,165评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,176评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,146评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,032评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,896评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,311评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,536评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,696评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,413评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,008评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,815评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,698评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,592评论 2 353

推荐阅读更多精彩内容