为什么containsKey()的Map只调用hashCode()?

时间:2014-05-22 21:21:24

标签: java hashmap equals

我阅读了Map的JavaDoc接口,它说:

  

Collections Framework接口中的许多方法都是根据equals定义的   方法。例如,containsKey(Object key)方法的规范说:   "当且仅当此映射包含键k的映射时才返回true   (key==null ? k==null : key.equals(k))&#34。不应解释该说明书   暗示使用非Map.containsKey参数null调用key将会   导致为任何键key.equals(k)调用k。实现是免费的   实现优化,从而避免equals调用,   例如,首先比较两个密钥的哈希码。   (Object.hashCode()规范保证两个对象具有不相等   哈希码不能相等。)

我的理解是当调用containsKey()时,都会调用hashCode()和equals(),因此我编写了自己的代码来测试它。

HappyDay类将作为键存储在HashMap中,我重写hashCode()和equals()方法,并添加System.out.println("invoking hashCode()" + this.happyHour);System.out.println("invoking equals()");来检查方法是否被调用。

public class HappyDay {

    private final int happyHour;

    public HappyDay(int hh) {
        this.happyHour = hh;
    }

    public int getHappyHour() {
        return this.happyHour;
    }

    @Override
    public boolean equals(Object o) {
        System.out.println("invoking equals()");
        if (o == null) {return false;}

        if (o == this) {return true;}

        //this is an easy overridden, if happy hour equal, objects will be equal.
        if (o instanceof HappyDay) {
            HappyDay other = (HappyDay) o;
            int otherHappyHour = other.getHappyHour();
            if (this.happyHour == otherHappyHour) {
                return true;
            }
        }
        return false;
    }

    @Override
    public int hashCode() {
        System.out.println("invoking hashCode()" + this.happyHour);
        int hash = 7;
        hash = hash + this.happyHour;
        return hash;
    }
}

public class Main {
    public static void main(String[] args) {
        Map<HappyDay,String> hm = new HashMap<>();

        HappyDay hd1 = new HappyDay(1);
        HappyDay hd2 = new HappyDay(2);

        hm.put(hd1, "hd1");
        hm.put(hd2, "hd2");

        if(hm.containsKey(hd2)){
            System.out.println("found");
        }else{
            System.out.println("not exist");
        }
    }
}

Main类是将两个HappyDay实例放入HashMap,插入(put()方法后),我调用hm.containsKey(hd2),正如我从JavaDoc引用的那样,它应首先调用hashCode()然后调用equals( ),但输出

invoking hashCode()1 //call put()
invoking hashCode()2 //call put()
invoking hashCode()2 //call containsKey()
found

我期待另一个输出行应该是invoking equals(),有人可以为我解释为什么不调用equals()吗?

2 个答案:

答案 0 :(得分:6)

哈希映射首先通过==检查密钥相等性; 只有在失败时才会继续检查equals

现在,您将hd2作为键添加到地图中,然后使用相同对象作为参数检查containsKey,因此==测试通过并且永远不会调用equals

检查地图是否包含给定密钥,直至检查getEntry(key)是否返回null。我们look at the source

360     final Entry<K,V> getEntry(Object key) {
361         int hash = (key == null) ? 0 : hash(key.hashCode());
362         for (Entry<K,V> e = table[indexFor(hash, table.length)];
363              e != null;
364              e = e.next) {
365             Object k;
366             if (e.hash == hash &&
367                 ((k = e.key) == key || (key != null && key.equals(k))))
368                 return e;
369         }
370         return null;
371     }

在第367行,我们可以看到在 ==测试之前执行了equals测试。如果||通过,则equals的{​​{3}}会完全跳过==测试,这就是这里发生的事情。

这可能是实现的,因为它是跳过可能昂贵的equals方法(例如String#equals必须检查给定字符串的每个字符)。 short-circuiting还指出如果o1.equals(o2) o1 == o2应该为真,那么这是有效的优化。


让我们稍微修改你的代码来进行健全性检查:

if (hm.containsKey(new HappyDay(2))) {
    System.out.println("found");
} else {
    System.out.println("not exist");
}

现在输出是:

invoking hashCode()1
invoking hashCode()2
invoking hashCode()2
invoking equals()
found

请注意,equals已被调用。这是有道理的,因为我们使用 new 但是相同的对象调用containsKey,因此==测试返回false并执行equals测试。

答案 1 :(得分:4)

The source code for HashMap告诉我们发生了什么。

containsKey方法调用getEntry

360     final Entry<K,V> More ...getEntry(Object key) {
361         int hash = (key == null) ? 0 : hash(key.hashCode());
362         for (Entry<K,V> e = table[indexFor(hash, table.length)];
363              e != null;
364              e = e.next) {
365             Object k;
366             if (e.hash == hash &&
367                 ((k = e.key) == key || (key != null && key.equals(k))))
368                 return e;
369         }
370         return null;
371     }

如果哈希值匹配,则首先将key==进行比较。由于您传入原始hd2对象,==运算符返回true(它是同一个对象),并且短路||运算符从不评估右侧,并且未调用equals

如果您要制作另一个HappyDay,同时传递2,那么==将会产生false,并且会调用equals

至于为什么==首先存在,我不得不猜测,首先执行快速==会更有效率,而且只会对{{{{{{ 1}}如果它们不是同一个对象。