1

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!

Lee
  • 119
  • 2
  • 7
    This is a terrible question - it depends on hash collision details of `Object.hashCode`. – user2357112 Jul 25 '18 at 04:29
  • @user2357112 Great point! I always override `hashCode`, but they did not in this question... – Lee Jul 25 '18 at 04:31
  • 3
    Possible duplicate of [Objects as Map keys without Hashcode and equals](https://stackoverflow.com/questions/6871929/objects-as-map-keys-without-hashcode-and-equals) – Lee Jul 25 '18 at 04:33
  • 1
    @Lee that's probably a trick question, given that they did not override hashcode makes it pretty unusable when using as a key in a map – Lino Jul 25 '18 at 04:36
  • Possible duplicate of [Why do I need to override the equals and hashCode methods in Java?](https://stackoverflow.com/questions/2265503/why-do-i-need-to-override-the-equals-and-hashcode-methods-in-java) – AxelH Jul 25 '18 at 05:29
  • Well @user2357112, as mention in the doc "_[...]This is typically implemented by converting the internal address of the object into an integer[...]_", so this is not really a terrible question, just showing the importance of overriding the `hashCode` method. Different instance will never have the same hashCode. Oracle tests are full of question like these to be honest. – AxelH Jul 25 '18 at 05:31
  • 1
    @AxelH: Different instances can totally have the same hashCode. For example, objects being moved in memory, or the simple effects of a 64-bit address space, can lead to identical address-based hash codes. Also, even without the hash codes being identical, objects can land in the same bucket, at which point the behavior relies on the undocumented, non-guaranteed details of whether the HashMap implementation performs a hash equality check before trying `equals`. – user2357112 Jul 25 '18 at 05:35
  • @user2357112 I don't believe there is any risk about object being moved in memory since here this will use the internal address, the one in the JVM and I don't think those change with time. This is only assumption! For the rest, Remember this is about a Java certification, probably the first level from this. So there is no need to go that far. I do agree that the collision risk about the integer value generate from a bigger value is a risk. But in this certification, this isn't "important" and is even mention in the javadoc with "_As much as is reasonably practical[...]_" – AxelH Jul 25 '18 at 05:57

4 Answers4

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!

Vivek Pakmode
  • 1,016
  • 2
  • 10
  • 22
  • why negative vote this answer is correct, add debug point at equals method without hashcode method and do in another way you will able to know – Ryuzaki L Jul 25 '18 at 05:38
1

If we keep this simple, since this is for a Java Certification.

Notice that MyKeys doesn't override hashCode, you know there will be something about it. And I usually try to remember only one thing about Object.hashCode

As much as is reasonably practical, the hashCode method defined by class Object does return distinct integers for distinct objects. (This is typically implemented by converting the internal address of the object into an integer, but this implementation technique is not required by the JavaTM programming language.)

Or in short, every instance will have a distinct hashcode. Meaning that with this code, every new MyKeys will add a new pair in the map.

In reality, this is a bit more complex since the method still return an integer, so the risk of collision is still present (integer doesn't provide infinite amount of values). You can see a bit more about this here.


This explain why the answer is that the map will have a size of 4. Each key inserted is a different instance.

AxelH
  • 14,325
  • 2
  • 25
  • 55
  • I'd like to hear more from the down voter, to my mind this is the right answer. [Here](https://srvaroa.github.io/jvm/java/openjdk/biased-locking/2017/01/30/hashCode.html) is an article investigating `Object.hashCode` implementation. – Paul Rooney Jul 25 '18 at 05:46
  • I will need to check that later, this is a github link and those are blocked at my office... Thanks @PaulRooney. And about the downvote, this is probably based on the comment on the question. – AxelH Jul 25 '18 at 05:49
0

As answered by others, the ans will be 4, reason being not overriding hashcode method.

For a more clear reason, whenever an object is added in hash map, the hashcode of the key is generated, which decides the location of the entry set. The 2 objects m1 and m3 will have different hash codes as the hashcode method is not overridden (usual hashcode behaviour). Different hash code will not create any collision and a new entry is made.

On the contrary, the equals methods is called only after the hashcode method produces the same result, i.e., same hash code.

In case of m2 and m4 also, the 2 objects have different hash codes, hence 2 different entries, with no calling done to the equals method.

Hence, in cases of hashing, it is necessary to overload hashcode method, along with equals.

Yashi Srivastava
  • 187
  • 3
  • 13
0

It will be more clear when we see the implementation of put method of HashMap.

// 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);
}

In your code you @Override only equals method.

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

To achieve output you need to override both hashCode() and equals() method.

gifpif
  • 4,507
  • 4
  • 31
  • 45