栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

通过源码看问题----包装类的等值判断问题

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

通过源码看问题----包装类的等值判断问题

问题引入       

        这两天在工作过程中,被同事提醒我的Integer类等值判断最好用equals判断,加上之前刚遇到过一个Long类型的相同数字使用 == 判断时返回false,不禁让我思考,为什么不可以用 == 号,之前用的时候为啥没遇到这种情况?Object对象的equals方法不也是比较的地址值吗?带着这些问题,我去翻了翻源码,结果发现,和我之前想的确实不一样。

结论

        先说结论,通过自动装箱得到的包装类中,Integer、Short、Byte、Long在值为-128~127的范围内是可以用 == 判断的,其中Integer的最大范围可以通过设置比127大;Character在0~127的范围是可以用 == 判断的;Boolean可以用 == 判断;Double和Float不可以用==判断。在工作学习中,最好养成用equals进行等值比较的习惯!

为什么会有部分值可以用 == 判断的情况

        首先我们要知道通过自动装箱的包装类是怎么得到的。

        我们都知道,java代码有些内容我们不写,系统在编译的时候是会自动帮我们加上的,就比如一个对象,不写构造方法的时候,系统会默认帮我们加上一个无参构造的语句。其实自动装箱同理,我们可以写成 Short i = 1; 的形式,但是在编译的时候,系统是调用的是Short.valueOf()方法得到的Short对象。例如

 Short s1 = Short.valueOf((short)1);
 Short s2 = Short.valueOf((short)1);
 System.out.println(s1 == s2);

        那么进去valueOf方法看看,应该就可以知道为什么会有部分值可以 == 判断了吧!

Integer

          首先看一下Integer.valueOf方法的内容。     

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

       这里先是一个判断,如果i在一个区间范围内,就返回一个数组里的内容,否则的话就new一个新的对象出来。那么,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() {}
    }

        通过查找可以发现,IntegerCache是Integer的一个静态内部类。有三个属性,分别是最低值,最高值,Integer数组;除此之外就只有一个静态代码块了。在静态代码块中一共做了三件事:1、给high属性赋值;2、创建一个Integer的数组,长度刚好是最大值到最小值的个数;3、将最小值和最大值区间的值依次new一个Integer对象存进数组中。

        其中high属性的赋值并不是直接就赋值了,而是做了一个判断,从一个地方取值,如果有值,和127比较取最大值,再与Integer的最大值(0x7fffffff)+128-1进行比较,取一个最小值赋给high属性。所以Integer的最大缓存值才可以自己设定。也不是没有边界的,最大不超过(Integer的最大值(0x7fffffff)+128-1),最小不低于127(后面的断言有做限制,如果小于127会报error )。

        看着这就明白了,这就是将区间内的值放到数组中缓存起来。再联系前面valueOf的代码,就顿悟了,原来它是先判断int值是否在缓存的值的区间内,如果是的话,就直接从缓存数组中取出返回,如果不是就自己new一个新对象。        

        所以说,以前自己在练习的时候都是用的100以内的数字,难怪会用 == 判断成功,他们返回的都是一个对象,比较地址值当然成功啦。

Byte

        接着来看一下Byte的valueOf()方法

  public static Byte valueOf(byte b) {
        final int offset = 128;
        return ByteCache.cache[(int)b + offset];
    }

        这个就更简单粗暴啦,直接返回ByteCache的缓存数组里的对象。那好,接着去看ByteCache里都有什么!

   private static class ByteCache {
        private ByteCache(){}

        static final Byte cache[] = new Byte[-(-128) + 127 + 1];

        static {
            for(int i = 0; i < cache.length; i++)
                cache[i] = new Byte((byte)(i - 128));
        }
    }

        我们都知道,byte的取值范围就是-128~127。所以这里面就直接将这些值存进了缓存数组中。所以不管你是Byte的什么值,通过valueOf()方法得到的,只要数字相同,他们的地址值也一定相同。

Short

        话不多说上代码

  public static Short valueOf(short s) {
        final int offset = 128;
        int sAsInt = s;
        if (sAsInt >= -128 && sAsInt <= 127) { // must cache
            return ShortCache.cache[sAsInt + offset];
        }
        return new Short(s);
    }
   private static class ShortCache {
        private ShortCache(){}

        static final Short cache[] = new Short[-(-128) + 127 + 1];

        static {
            for(int i = 0; i < cache.length; i++)
                cache[i] = new Short((short)(i - 128));
        }
    }

        可以看到ShortCache和ByteCache都差不多的内容,也是for循环缓存-128~127之间的值的对象。如果在缓存区间,就从缓存数组里取,不同才重新new一个对象出来。

Long

        酒喝干再斟满,再看源码不嫌烦!

        

   public static Long valueOf(long l) {
        final int offset = 128;
        if (l >= -128 && l <= 127) { // will cache
            return LongCache.cache[(int)l + offset];
        }
        return new Long(l);
    }
   private static class LongCache {
        private LongCache(){}

        static final Long cache[] = new Long[-(-128) + 127 + 1];

        static {
            for(int i = 0; i < cache.length; i++)
                cache[i] = new Long(i - 128);
        }
    }

         还是同样的配方,还是同样的味道,依旧是缓存-128~127之间的值的对象,在调用valueOf()方法的时候,还是判断,在区间内就从缓存数组中取对象,不在就重新new一个出来。

Character

        当你看到这个的时候,就证明你已经看了一半了!

  public static Character valueOf(char c) {
        if (c <= 127) { // must cache
            return CharacterCache.cache[(int)c];
        }
        return new Character(c);
    }
  private static class CharacterCache {
        private CharacterCache(){}

        static final Character cache[] = new Character[127 + 1];

        static {
            for (int i = 0; i < cache.length; i++)
                cache[i] = new Character((char)i);
        }
    }

        可以看到,和前面三个基本上不会有太大的差别,唯一的区别就是缓存的0~127的值

Boolean

        前面的cache看吐了吗,别慌,新鲜的来了~

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

        咦,终于不是缓存里取了,那它是从哪拿的呢,点开可以发现,其实都差不多,因为boolean只有两个值,所以不搞数组啦,直接声明两个静态常量好啦!

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

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

        所以,还是的,不管你通过valueOf()方法拿多少个对象, 最终指向的都是这两个。那 == 比较肯定行啊!

Double和Float

        喂?我不要面子的?前面兄弟们都单独讲,到我俩就没牌面了?

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

        确实,因为他俩既简单又相似,就干脆放一块好了。可以看出,他俩是直接就new对象的,所以自动装箱时,这两个万万不可用 == 进行比较,一定是不同的对象嘛!

如何等值判断---equals

        前面划了那么多,大家都知道在装箱的时候他们是如何产生的吧。装箱用 == 都会有问题,那直接new 出来的对象就更不可能用 == 进行判断啦。那咋办嘛,祈祷equals被重写了呗!

他们的equals判断都是重写过的方法,而且基本上不会有很大差别,这里就只挑一个进行说了

   public boolean equals(Object obj) {
        if (obj instanceof Integer) {
            return value == ((Integer)obj).intValue();
        }
        return false;
    }

都是有判断传入的对象是否是包装类的类型,然后再去判断具体的值是否相等。

好啦,以上就是本次分享的全部内容了。如有问题,欢迎指正!

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/270578.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号