Peculiar HashMap Behavior

时间:2018-07-25 04:27:42

标签: java hashmap

I was reviewing one of Oracle’s Java Certification Practice Exams when I came across the follow question:

Given:

class MyKeys {
    Integer key;
    MyKeys(Integer k) {
        key = k;
    }
    public boolean equals(Object o) {
        return ((MyKeys) o).key == this.key;
    }
}

And this code snippet:

Map m = new HashMap();
MyKeys m1 = new MyKeys(1);
MyKeys m2 = new MyKeys(2);
MyKeys m3 = new MyKeys(1);
MyKeys m4 = new MyKeys(new Integer(2));
m.put(m1, "car");
m.put(m2, "boat");
m.put(m3, "plane");
m.put(m4, "bus");
System.out.print(m.size());

What is the result?

A) 2

B) 3

C) 4

D) Compilation fails

My guess was B because m1 and m3 are equal due to their key references being the same. To my surprise, the answer is actually C. Does put() do something that I am missing? Why wouldn’t "plane" replace "car"? Thank you!

4 个答案:

答案 0 :(得分:1)

With given definition of class i.e

class MyKeys {
    Integer key;
    MyKeys(Integer k) {
        key = k;
    }
    public boolean equals(Object o) {
        return ((MyKeys) o).key == this.key;
    }
}

It will result ans = 4, it has only equal method, if you add definition of hashcode then it will result ans=3

class MyKeys {
    Integer key;
    MyKeys(Integer k) {
        this.key = k;
    }

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

    @Override
    public int hashCode(){
        return key*key;
    }
}

Contract of equal and hashcode:

If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result. If you only override equals() and not hashCode() your class violates this contract.

The problem you will have is with collections where unicity of elements is calculated according to both .equals() and .hashCode(), for instance keys in a HashMap. If you have two objects which are .equals(), but have different hash codes, you lose!

答案 1 :(得分:1)

如果我们保持简单,因为这是Java认证。

请注意,MyKeys不会覆盖hashCode,因为您知道会有一些问题。而且我通常会只想起关于Object.hashCode

的一件事
  

在合理可行的范围内,Object类定义的hashCode方法会为不同的对象返回不同的整数。 (通常通过将对象的内部地址转换为整数来实现,但是JavaTM编程语言不需要这种实现技术。)

或者简而言之,每个实例将具有不同的哈希码。意味着使用此代码,每个新的MyKeys都会在地图中添加一对新的对子。

实际上,这有点复杂,因为该方法仍返回整数,因此仍然存在冲突的风险(整数不提供无限数量的值)。您可以了解有关此here的更多信息。


这解释了为什么答案是映射的大小为4。每个插入的键都是不同的实例。

答案 2 :(得分:0)

正如其他人回答的那样,ans为4,原因是未覆盖哈希码方法。

出于更明确的原因,每当在哈希映射中添加对象时,都会生成键的哈希码,该哈希码决定条目集的位置。两个对象 m1和m3将具有不同的哈希码,因为哈希码方法不会被覆盖通常的哈希码行为)。不同的哈希码不会产生任何冲突,因此会创建一个新条目。

相反,仅在hashcode方法产生相同的结果(即相同的哈希码)之后才调用equals方法。

在m2和m4的情况下,这两个对象具有不同的哈希码,因此具有2个不同的条目,而没有对equals方法进行调用。

因此,在散列的情况下,有必要重载hashcode方法以及等号

答案 3 :(得分:0)

当我们看到HashMap的put方法的实现时,将会更加清楚。

// here hash(key) method is call to calculate hash.
// and in putVal() method use int hash to find right bucket in map for the object.
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);
}

在您的代码中,@ Override仅等于方法。

class MyKeys {
    Integer key;
    MyKeys(Integer k) {
        key = k;
    }
    public boolean equals(Object o) {
        return ((MyKeys) o).key == this.key;
    }
}

要实现输出,您需要同时覆盖hashCode()和equals()方法。