Question

I know there is a need to override hashcode whenever the equals method is overridden in Java. That is merely a contract. I am trying to understand the logic behind this. I was reading *Effective Java by Joshua Bloch, and I came across this code (Item 9, page 45):

import java.util.HashMap;
import java.util.Map;

public final class PhoneNumber {
    private final short areaCode;
    private final short prefix;
    private final short lineNumber;

    public PhoneNumber(int areaCode, int prefix, int lineNumber) {
        rangeCheck(areaCode, 999, "area code");
        rangeCheck(prefix, 999, "prefix");
        rangeCheck(lineNumber, 9999, "line number");
        this.areaCode = (short) areaCode;
        this.prefix = (short) prefix;
        this.lineNumber = (short) lineNumber;
    }

    private static void rangeCheck(int arg, int max, String name) {
        if (arg < 0 || arg > max)
            throw new IllegalArgumentException(name + ": " + arg);
    }

    @Override
    public boolean equals(Object o) {
        if (o == this)
            return true;
        if (!(o instanceof PhoneNumber))
            return false;
        PhoneNumber pn = (PhoneNumber) o;
        return pn.lineNumber == lineNumber && pn.prefix == prefix
                && pn.areaCode == areaCode;
    }

    // Broken - no hashCode method!

    // A decent hashCode method - Page 48
    // @Override public int hashCode() {
    // int result = 17;
    // result = 31 * result + areaCode;
    // result = 31 * result + prefix;
    // result = 31 * result + lineNumber;
    // return result;
    // }

    // Lazily initialized, cached hashCode - Page 49
    // private volatile int hashCode; // (See Item 71)
    //
    // @Override public int hashCode() {
    // int result = hashCode;
    // if (result == 0) {
    // result = 17;
    // result = 31 * result + areaCode;
    // result = 31 * result + prefix;
    // result = 31 * result + lineNumber;
    // hashCode = result;
    // }
    // return result;
    // }

    public static void main(String[] args) {
        Map<PhoneNumber, String> m = new HashMap<PhoneNumber, String>();
        m.put(new PhoneNumber(707, 867, 5309), "Jenny");
        System.out.println(m.get(new PhoneNumber(707, 867, 5309)));
    }
}

This is what he mentions in the text, which I am having difficulty understanding.

At this point, you might expect m.get(new PhoneNumber(707, 867, 5309)) to return "Jenny", but it return null. Notice that two PhoneNumber instances are involved: one is used for insertion into the HashMap and a second, equal, instance is used for (attempted) retrieval. The PhoneNumber class's failure to override hashCode causes the two equal instances to have unequal hashcodes, in violation of the hashcode contract. Therefore the get method is likely to look for the phone number in a different hash bucket from the one in which it was stored by the put method

I don't understand what the two PhoneNumber instances he talks about. There is only instance that I create in m.put(new PhoneNumber(707, 867, 5309), "Jenny"). Also I look for this object again, which should return the same hashcode even if it inherits the hashCode method from Object Class. Why does this happen? Some explanation here would help a lot.

Was it helpful?

Solution

I don't understand what the two PhoneNumber instance he talks about.

The first one is the one you used for insertion.

m.put(new PhoneNumber(707, 867, 5309), "Jenny"));

The second instance is the one used for retrieval.

m.get(new PhoneNumber(707, 867, 5309)); // Notice the use of "new"

Also I look for this object again, which should return the same hashcode even if it inherits the hashCode method from Object Class.

That's incorrect. The default implementation of hashCode() in Object class returns distinct integers for distinct objects because it is implemented by converting the internal address of the object into an integer. Hence, the hash code check fails there.

If on the other hand, you had tried to retrieve the PhoneNumber using the same instance

PhoneNumber phoneNum = new PhoneNumber(707, 867, 5309);
m.put(phoneNum, "Jenny"));
m.get(phoneNum); // Not NULL

The hash code check would pass (since, you've used the same object that was inserted before) and the equals() as well. This of course isn't the recommended approach because we're far more likely to use a different key object than the one used for insertion.

The key used would, however, be meaningfully equivalent (like a different String object but whose text is the same) and hence providing a hashCode() implementation is required for the match and retrieval to happen correctly.

Also see: Checking for and Removing elements in Java HashMap

OTHER TIPS

If you don't override hashcode along with equals then every instance, e.g. "new PhoneNumber(707, 867, 5309)", will have a different hashcode.

So from a HashMap perspective they will be treated as two different entries. Just read more about how hashmap works. So if two objects that may be equal, but have a different hascode, will be stored in different buckets.

Go to this link

The hashcode is used to maintain the contract and uniquely identify each object in hashmap or hashtable.

For your question.

Also I look for this object again, which should return the same hashcode even if it inherits the hashCode method from Object Class.

Check the Object#hashCode documentation here

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.)

In other words, hashCode won't return same integer for two objects unless you overwrite it.

The purpose of hashCode() is to quickly identify things to which an object is not equal; one call to hashCode() will instantly reveal that an object is not equal to any object whose has hashCode() method has been called and has returned a different value. This is a pretty powerful ability, but it requires that any two objects that are "equal" must return the same hashCode value. If two objects don't return the same hashCode value, some collection types will assume that they can't possibly be equal and won't bother calling equals to see if they might be.

You are thinking you have only one instance, but you have two instances. Each

new PhoneNumber(707, 867, 5309)

creates an instance at another memory place. The hash map method m.get is looking for the new instance which you are create in the method call. This instance has another hash code to the first created instance in the m.put method.

(Because there are two object instances, Java will compute a different hashCode in the super class Object.)

So the hash map could not find the first object with the hashCode of the second object.

Please store the phone number in a variable, and use the variable to put and to get so it will be works - because it is the same object at the same memory place with the same hashCode and the equals == true.

public static void main(String[] args) {
    Map<PhoneNumber, String> m = new HashMap<PhoneNumber, String>();
    PhoneNumber num = new PhoneNumber(707, 867, 5309);
    m.put(num, "Jenny");
    System.out.println(m.get(num));
}

But for a real usage you must correctly implement the hashCode and the equals methods.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top