Before moving to the explanation we will check the contract, copied from the Object specification [JavaSE6]:
- Whenever it is invoked on the same object more than once during an execution
of an application, the hashCode method must consistently return the
same integer, provided no information used in equals comparisons on the
object is modified. This integer need not remain consistent from one execution
of an application to another execution of the same application.
- 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.
- It is not required that if two objects are unequal according to the equals(Object)
method, then calling the hashCode method on each of the two objects
must produce distinct integer results. However, the programmer should be
aware that producing distinct integer results for unequal objects may improve
the performance of hash tables.
First we consider the String object.
Assume I create two String instances (without using new keyword) like as bellow,
String s1 = "Hello World";
String s2 = "Hello World";
When we print s1 == s2 it should print true. Because these two String references refer the same location.
Likewise s1.equals(s2) is also returns true.
Note: When we create the two different String instance(without new keyword) with the same value, both references refer the same location in memory.
Now we create two different String objects(with new keyword) but the contents are same.
String s1 = new String("Hello World");
Strung s2 = new String("Hello World");
When we print s1.equals(s2) it should print true because the the String equals methods checks the content of the String instance. If the content is true then the equals methods returns true.
According to the java contract if equals objects returns true the the hashCode also should returns true(this is not applicable for all scenarios). So s1.equals(s2) returns true.
s1 == s2 print false as these are two different objects.
Now we move to the real word objects
Assume we already have the Student class with two properties studentId, name, and age.
public class Student {
private int studentId;
private String name;
private int age;
public int getStudentId() {
return studentId;
}
public void setStudentId(int studentId) {
this.studentId = studentId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
Now I am creating two instances for student class.
Student student1 = new Student();
student1.setStudentId(11111);
student1.setName("K.Senthuran");
student1.setAge(29);
Student student2 = new Student();
student2.setStudentId(11111);
student2.setName("K.Senthuran");
student2.setAge(29);
We will discuss what is the output for following print statements.
1. student1 == student2
With out any confusion we can tell the output should be false as these student1 and student2 are two different objects.
2. student1.equals(student2)
This prints false because when this statement executes, it calls the Object equals() method. The current implementation for the object equals() method is as following,
public boolean equals(Object obj) {
return (this == obj)
}
So this should print false.
3. student1.hashCode() == student2.hashCode()
This returns false because the two different objects have different hashcodes.
Now we override the equals method to return true for the objects which has same contents.
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Student other = (Student) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (studentId != other.studentId)
return false;
return true;
}
Now if we print student1.equals(student2) it should print true (According to the above equals() method implementation).
According to the contract 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. But when we print the hash code for these two objects those return different value.
So we break the contract by overriding the equals() method. Check the bellow example.
For example, consider the following simplistic PhoneNumber class, whose equals method is constructed:
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 numberthis.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!
... // Remainder omitted
}
Suppose you attempt to use this class with a HashMap:
Map<PhoneNumber, String> m = new HashMap<PhoneNumber, String>();
m.put(new PhoneNumber(707, 867, 5309), "Jenny");
hashCode causes the two equal instances to have unequal hash codes, 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. Even if the two instances happen to hash to the same bucket, the get
method will almost certainly return null, as HashMap has an optimization that caches the hash code associated with each entry and doesn’t bother checking for object equality if the hash codes don’t match. Fixing this problem is as simple as providing a proper hashCode method for the PhoneNumber class as bellow.
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;
}
Now we can access the value "Jenny" by using the key new PhoneNumber(707, 867, 5309).
Likewise if we want to avoid the contract problem mentioned in Student class we need to override the hashcode() method properly as bellow,
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + studentId;
return result;
}
Conclusion: If we override the equals() method we should override the hashCode() method also.