HashMap中entry.hash == hash && (k == key || key.equals(k))

351 阅读3分钟

背景知识

1.Java对象的hashCode与内存地址的关系

参考这篇文章。 可以得出如下结论:

  1. hashcode不一定能代表内存地址不相同。不同的JVM有不同的实现。openjdk 源码里生成 hashCode算法有六种,可以通过-XX:hashCode=N指定,N取值0到5。n=1时算法取值为对象的内存地址,默认是n=5算法。
/** 启动该单元测试时,指定JVM参数-XX:hashCode=2 这个参数让所有对象的hashCode为2*/
public class HashCodeTest {
    @Test
    public void test() {
        Demo d1 = new Demo("1");
        System.out.println("#### hashCode d1 : " + d1.hashCode());
        Demo d2 = new Demo("1");
        System.out.println("#### hashCode d2 : " + d2.hashCode());
    }
}

class Demo {
    String key;

    Demo(String key) {
        this.key = key;
    }
}

输出如下

#### hashCode d1 : 2
#### hashCode d2 : 2

2.object1 == object2 比较的是hashcode还是内存地址

重写hashCode,让每个对象返回的hashCode值都一样

public class HashCodeTest {
    @Test
    public void test() {
        Demo d1 = new Demo("1");
        System.out.println("#### hashCode d1 : " + d1.hashCode());
        Demo d2 = new Demo("1");
        System.out.println("#### hashCode d2 : " + d2.hashCode());

        System.out.println("#### hashCode is equal : " + (d1.hashCode() == d2.hashCode()));
        System.out.println("#### memory address is equal : " + (d1 == d2));
    }
}

class Demo {
    String key;

    Demo(String key) {
        this.key = key;
    }

    @Override
    public boolean equals(Object o) {
        Demo d = (Demo) o;
        return this.key.equals(d.key);
    }

    @Override
    public int hashCode() {
        return 111;
    }
}

输出如下

#### hashCode d1 : 111
#### hashCode d2 : 111
#### hashCode is equal : true
#### memory address is equal : false

通过如上代码可以得出结论:object1 == object2 比较的是内存地址,非hashCode

3.HashMap中entry.hash == hash && (k == key || key.equals(k))理解

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

entry.hash == hash && (k == key || key.equals(k))HashMapgetput方法都会用到。 在HashMap中寻找key对应的映射关系时,如果上述判断条件成立,则代表map中存在对应映射关系。

1.entry.hash == hashHashMap中,会对keyhashCode值进行二次hash计算((h = key.hashCode()) ^ (h >>> 16)),接下来在计算该key在数组中索引位置时,通过hash & (length - 1)进行的计算。因此,不同的hash值计算得到的索引位置可能相同。

2.(k == key || key.equals(k))先是比较内存地址是否相等,如果内存地址不相等,再去使用equals方法判断两个对象是否相等。

2.1k == key

k == key这里比较的是内存地址,防止hashmap重复放入同一个对象(同一个对象的内存地址是相同的,如果k == key成立说明放入的是同一个对象,没必要再去执行equals方法)

2.2key.equals(k)

先说明hashCode 和 equals的关系:

1.两个对象equals的时候,hashCode必须相等。换句话说,两个对象hashCode不相等,那么equals一定不相等

2.hashCode相等,对象不一定equals。

根据1和2的描述,在HashMap中,entry.hash == hash似乎是多余的。假设去掉这个判断,如果两个key的hashCode不相等,那么就会去执行equals方法,根据1的描述,hashCode不相等,equals方法也不会相等。有无entry.hash == hash语句,判断结果都是相同的,那么这里为什么要加上这个判断呢?

因为如果没有entry.hash == hash这个判断,所有的都会执行equals方法,效率比较低。如果hashCode不相等,那么equals方法也不会相等,没必要再去执行equals方法。