Short/Integer/Long以及Character包装类的缓存问题

一、问题出现

我们都知道,在Java代码中,对于基本类型的值比较可以使用"==",比如下面的代码:

        int a = 10, b = 10;
        // true
        log.info("value is {}", a == b);
        long c = 12L, d = 21L;
        // true
        log.info("value is {}", a == b);

而对于非基本类型而言,特别是对象,我们在使用“==”则是比较的它们在内存中的地址,而非它们的值,比如:

        String a = new String("a"), b = new String("a");
        // false
        log.info("value is {}", a == b);

如果是包装类型呢,int对应的Integer,long对应的Long,它们应该也属于对象范畴,会发生什么呢?

        Integer a = 100, b = 100, c = 151, d = 151;
        // true
        log.info("value is {}",a == b);
        // false
        log.info("value is {}", c == d);

        Long e = 121L,f = 121L,g = 163L,h = 163L;
        // true
        log.info("value is {}",e == f);
        // false
        log.info("value is {}", g == h);

结果如上,是不是感觉不可思议?为什么会出现这样的结果呢?

二、源码解析

我们以Integer的源码为例,当我们声明一个Integer类型的实例时,会调用Integer中的valueOf方法进行创建:

    /**
     *
     * This method will always cache values in the range -128 to 127,
     * inclusive, and may cache other values outside of this range.
     *
     */
    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

我们结合注释和源码就可以知晓,倘若Integer实例化的数值是[-128,127]之间的,那么就会使用已经提前缓存下来的对象实例,不会额外去创建新的该值实例;而如果Integer实例化的数值不在这个区间,那么每次实例化的值都是通过新建一个对象来完成的。

所以我们就可以解释如上代码示例中,a和b,e和f,都是在这个范围区间的,它们都是代表缓存中的该值的实例对象,指向的都是该对象的内存地址空间,所以才有a=b,e=f;而c和d,e和f则分别是不同的对象,它们的内存地址空间当然是不同的。

这个问题搞清楚了,那么为什么要缓存[-128,127]这个区间呢,而不是别的区间?

我们再来看看IntegerCache的源码:

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]是编程中比较常用到的数值,所以对其进行缓存,这是一种空间换时间的策略。如果开发者认为最大值偏大或者偏小,可以自行对虚拟机设置参数进行调节,使得缓存的数值更契合自己的使用场景。

所以说,我们在上面使用的包装类示例中的运行结果并不是一直都是那样的,只有在默认情况下才是那样。

三、解决方法

上面我们只是以包装类Integer的源码为例子,其实其它的包装类型比如Long、Short、Character,查看它们的源码发现,缓存的方法和范围略有不同,但是原理都一样的。

我们在日常开发中,这就是一个大坑,并不能保证所有开发人员都知道这个,所以才规定,对于对象,包括包装类型,如果要比较它们值的大小是否相等,必须使用equals方法,禁止使用==

你可能感兴趣的:(Short/Integer/Long以及Character包装类的缓存问题)