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

时间:2022-04-22
本文章向大家介绍程序猿的日常——Java基础之equals与hashCode,主要内容包括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方法不同,那么对象就不同;否则,对象相同