JDK源码解析

Java自动拆箱和装箱

2019-08-07  本文已影响0人  12313凯皇

参考文章深入剖析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那样做一个缓存。

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

  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方法的实现,但是这里可能还是会犯迷糊,下面先提供两点提示:

四、总结

上一篇下一篇

猜你喜欢

热点阅读