6

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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
brain storm
  • 30,124
  • 69
  • 225
  • 393
  • You will find a good explanation here http://www.thejavageek.com/2013/06/28/significance-of-equals-and-hashcode/ – Prasad Kharkar Aug 24 '13 at 05:48
  • In an extremely non technical way of thinking about it; you can consider the hashcode as a (quick) hint at equality whereas the equals method (slowly) settles the matter. If the two aren't in tandem then clearly mad things may happen – Richard Tingle Aug 24 '13 at 10:29

6 Answers6

4

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

Community
  • 1
  • 1
Ravi K Thapliyal
  • 51,095
  • 9
  • 76
  • 89
  • @BalusC Aww, I was so happy that my profile kinda caught your attention and then I read the link and all the excitement fizzled away :( Actually, all the head hunters here in India are still stuck searching with `J2EE` as their keyword and so my actual resume has it as `JEE/J2EE` :) And, having it as `Java/Java EE` here kinda looked weird and was messing up the *Justify* text alignment that I was going for in the hover pop-up profile box. I'm kinda OCD on alignments :) Sorry, if this was too chatty for you. – Ravi K Thapliyal Oct 03 '13 at 14:59
3

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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Lokesh
  • 7,810
  • 6
  • 48
  • 78
0

Go to this link

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

Rajnish Mishra
  • 826
  • 5
  • 21
0

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.

Cao Dongping
  • 969
  • 1
  • 12
  • 29
0

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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Huluvu424242
  • 756
  • 10
  • 25
0

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.

supercat
  • 77,689
  • 9
  • 166
  • 211