美文网首页
Short/Integer/Long以及Character包装类

Short/Integer/Long以及Character包装类

作者: 文景大大 | 来源:发表于2020-02-12 21:08 被阅读0次

    一、问题出现

    我们都知道,在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包装类

          本文链接:https://www.haomeiwen.com/subject/bbfhfhtx.html