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.
Go to this link
The hashcode is used to maintain the contract and uniquely identify each object in hashmap or hashtable.
The first one is the one you used for insertion.
The second instance is the one used for retrieval.
That's incorrect. The default implementation of
hashCode()
inObject
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 instanceThe 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
For your question.
Check the Object#hashCode documentation here
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 tohashCode()
will instantly reveal that an object is not equal to any object whose hashashCode()
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 samehashCode
value. If two objects don't return the samehashCode
value, some collection types will assume that they can't possibly be equal and won't bother callingequals
to see if they might be.You are thinking you have only one instance, but you have two instances. Each
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.
But for a real usage you must correctly implement the
hashCode
and theequals
methods.If you don't override
hashcode
along withequals
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.