• 程序猿的日常——Java基础之equals与hashCode


    equals和hashCode是我们日常开发最常使用的方法,但是因为一般都使用默认的规则,因此也很少会引起关注。不过了解他们的用途和设计的原则,还是会帮助我们更好的设计代码。

    equals

    equals是java很基础的一个问题,通常都会跟==来做比较。那么看看下面的问题:

    int a = 1;
    int b = 1;
    System.out.println(a==b);//true
    Integer a1 = new Integer(1);
    Integer a2 = new Integer(1);
    System.out.println(a1==a2);//false
    System.out.println(a1.equals(a2));//true
    

    这是因为,==比较的是引用,而equals比较的内容,进入到Integer的源码中,就可以看到它其实重写了equals方法:

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

    说到equals,就不得不说如果我们想要自己重写equasl都需要注意哪些地方。首先就是equals方法的设计需要满足下面几个特性:

    1. 自反性:对于所有的非null,a=a
    2. 对称性:a=b,则b=a
    3. 传递性:a=b,b=c,则a=c
    4. 一致性:对于没有被修改的ab,如果a=b,则一直a=b
    5. 非空性:对于任何非Null,a!=null

    因此推荐最佳的equals设计方法应该是

    @Override 
    public boolean equals(Object o){
        if(o == this)
            return true;
        if(!(o instanceof XX))
            return false;
        XX xx = (XX)o;
        return xx.x.equals(o.x);//TODO
    }
    

    另外,还需要注意的是,如果覆盖了equals方法,那么还需要注意它的hashCode.

    hashCode

    这个hashCode其实是有特定的使用场景的,比如List或者数组就不会用到hashCode,如果是HashMap、HashSet、HashTale,那么hashCode就十分重要了。

    这是因为在类似HashSet的集合中,是需要对元素去重,那么如何判断两个元素是相同的呢?如果每次调用equals,那么代价有点大,这是因为如果对象内部的属性很多,equals一般需要对比每一项。所以就是用hashCode作为对比的方法。

    下面看看Hashtable中添加一个元素的逻辑:

    public synchronized V put(K key, V value) {
            // 检查value是否为空
            if (value == null) {
                throw new NullPointerException();
            }
    
            // 确保key没有在table中
            Entry<?,?> tab[] = table;
            int hash = key.hashCode();//获取key的hash值
            // 通过对table的length取余,确定对应的存储位置
            int index = (hash & 0x7FFFFFFF) % tab.length;
            // 挨个对比是否相同
            Entry<K,V> entry = (Entry<K,V>)tab[index];
            for(; entry != null ; entry = entry.next) {
                if ((entry.hash == hash) && entry.key.equals(key)) {
                    V old = entry.value;
                    entry.value = value;
                    return old;
                }
            }
    
            addEntry(hash, key, value, index);
            return null;
        }
    

    在HashMap和HashSet中比较类似,都是下面的逻辑:

    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
    
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
    

    可以看到它们都使用了HashCode,只不过是方式方法不一样而已。

    在不同的对象中hashCode的生成规则也是不一样的,不过最终的目的都是返回一个int值。比如,在Integer中:

    public static int hashCode(int value) {
        return value;
    }
    

    在String中:

    public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;
    
            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }
    

    因此设计hashCode,需要遵循下面的原则:

    1. 在程序期间,同一个对象调用hashCode,返回的是一个数
    2. 如果x.equals(y)返回true,那么它们的hashCode肯定也相等
    3. 如果x.equals(y)返回false,那么它们的hashCode有可能相等,也有可能不相等。

    换句话说,判断两个对象是否相等:

    1. 如果他们的hashCode不相等,那么它们肯定不想等;如果相同,还得对比equals
    2. 如果两个对象的equals方法不同,那么对象就不同;否则,对象相同
  • 相关阅读:
    典型用户
    站立会议5
    站立会议4
    《构建之法》阅读笔记05-需求分析
    站立会议3
    编写Android程序Eclipse连不上手机。
    站立会议2
    站立会议1
    第七周学习进度
    shiro之 散列算法(加密算法)
  • 原文地址:https://www.cnblogs.com/xing901022/p/7739902.html
Copyright © 2020-2023  润新知