自动装箱与拆箱

最近遇到一个面试题,是关于自动装箱和拆箱相关的,代码如下所示

public static void main(String[] args)  {

         Integer f1 = 100, f2 = 100, f3 = 150, f4 = 150;
         System.out.println(f1 == f2); //true
         System.out.println(f3 == f4); //false
    }

老规矩反编译一下,代码如下:

public class Test
{
  public static void main(String[] args)
  {
    Integer f1 = Integer.valueOf(100); Integer f2 = Integer.valueOf(100); Integer f3 = Integer.valueOf(150); Integer f4 = Integer.valueOf(150);
    System.out.println(f1 == f2);
    System.out.println(f3 == f4);
  }

答题准备:知识点复习

  • 自动装箱
    • 定义:装箱就是自动将基本数据类型转换为包装器类型。
    • 实质:调用Integer.valueOf()
  •  自动拆箱
    • 定义:自动将包装器类型转换为基本数据类型
    • 实质:调用Integer.intValue()

自动装箱拆箱过程如下代码:

    public static void main(String[] args)  {

        //自动装箱
         Integer boxing = 99;

         //自定拆箱
         int unpacking = boxing;
           }

反编译后代码如下:

  public static void main(String[] args)
  {
    Integer boxing = Integer.valueOf(99);

    int unpacking = boxing.intValue();
  }

解题前言:装箱与拆箱类型有很多,我们在这里只以Integer类型举例

  1. 基本类型:byte、short、int、long、float、double、char、boolean
  2. 基本引用类型:Byte、Short、Integer、Long、Float、Double、Character、Boolean

(1). 看到f1 == f2 我们第一想到的是 f1与f2都是引用类型,比较的是地址,所以得出结论是false。然而看到结果f1 ==f2 是true,就一大堆问号飘过。废话不多说,我们追本溯源研究一下源代码吧,点进Integer.valueOf(100)方法,我们看到如下代码:

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

它会首先判断i的大小:如果 i 大于等于IntegerCache.low与 i 小于等于IntegerCache.high时调用IntegerCache.cache[i + (-IntegerCache.low),否则new Integer(i)对象。要想知道 i 的范围,我们又得研究一下IntegerCache,通过IDEA工具点击进入发现IntegerCache是Integer的内部类,其代码如下所示:

 /**
     * Cache to support the object identity semantics of autoboxing for values between
     * -128 and 127 (inclusive) as required by JLS.
     *
     * The cache is initialized on first usage.  The size of the cache
     * may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
     * During VM initialization, java.lang.Integer.IntegerCache.high property
     * may be set and saved in the private system properties in the
     * sun.misc.VM class.
     */

    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) {
                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 IntegerCache.high >= 127;
        }

        private IntegerCache() {}
    }

查看内部类上面的注释,大致意思是 缓存支持JLS要求的-128和127(含)。缓存在首次使用时会初始化(在常量池中)。缓存的大小可以由{@code-xx:autoboxcachemax=<size>}选项控制。在虚拟机初始化期间,java.lang.Integer.IntegerCache.High属性可以设置并保存在sun.misc.vm类(配置类)。

看到上述解释以及代码,我们得出结论:当传入某个值大于等于IntegerCache.low(-128)且小于等于IntegerCache.high(默认127)时调用IntegerCache.cache[i + (-IntegerCache.low)(初始化时缓存的[-128, 127] 值),否则new Integer(i)对象。然后回过来看看f1和f2,其值[-128, 127]范围内,并没有重新new一个对象,这就不难得出f1 == f2是 true了。

(2).同理(一), 我得出150并不在[-128, 127]范围内,所以f3和f4都new一个新对象放在堆中,这就不难得出f3 == f4是 false了。

总结

其他封装类类型初始化值范围

类型初始化值范围
Integer[-128,127]
Byte[-128,127]
BooleanTRUE,FALSE
Short[-128,127]
Long[-128,127]
Float
Double
Character(~,127]

Integer的JVM简易图

题内以及题外知识点归纳

  • 引发装箱和拆箱
    • 当两种不同类型用==比较时,包装器类的需要拆箱, 当同种类型用==比较时,会自动拆箱或者装箱
    • equals(Object o) 因为原equals方法中的参数类型是封装类型,所传入的参数类型(a)是原始数据类型,所以会自动对其装箱,反之,会对其进行拆箱
  • 封装类型在首次使用时会将某个范围值缓存初始化(在常量池中),在装箱过程中,如果不在其范围内会重新new一个全新的对象,否则取常量池中的对象
  • 装箱操作会创建对象,频繁的装箱操作会消耗许多内存,影响性能,所以可以避免装箱的时候应该尽量避免。
02-13 15:47