According to the \[[API 06|AA. Java References#API 06]\] class {{java.lang.Object}} documentation: |
If two objects are equal according to the
equals(Object)method, then calling thehashCodemethod on each of the two objects must produce the same integer result.
Failure to follow this contract is a common source of bugs. Notably, immutable objects need not override the hashcode() method.
Even when the equals method conveys logical equivalence between classes, the hashCode method returns distinct numbers as opposed to returning the same values, as expected by the contract. This noncompliant example stores a credit card number into a HashMap and retrieves it. The expected retrieved value is Java, however, null is returned instead. The reason for this erroneous behavior is that the hashCode method is not overridden which means that a different bucket would be looked into than was used to store the original value.
import java.util.Map;
import java.util.HashMap;
public final class CreditCard {
private final int number;
public CreditCard(int number) {
this.number = (short) number;
}
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof CreditCard))
return false;
CreditCard cc = (CreditCard)o;
return cc.number == number;
}
public static void main(String[] args) {
Map m = new HashMap();
m.put(new CreditCard(100), "Java");
System.out.println(m.get(new CreditCard(100)));
}
}
|
This compliant solution shows how {{hashCode}} can be overridden so that the same value is generated for any two instances that compare equal when {{Object.equals()}} is used. Joshua Bloch discusses the recipe to generate such a hash function in good detail \[[Bloch 08|AA. Java References#Bloch 08]\]. |
import java.util.Map;
import java.util.HashMap;
public final class CreditCard {
private final int number;
public CreditCard(int number) {
this.number = (short) number;
}
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof CreditCard))
return false;
CreditCard cc = (CreditCard)o;
return cc.number == number;
}
public int hashCode() {
int result = 7;
result = 37*result + number;
return result;
}
public static void main(String[] args) {
Map m = new HashMap();
m.put(new CreditCard(100), "Java");
System.out.println(m.get(new CreditCard(100)));
}
}
|
Overriding the method equals() without correspondlingly overriding the method hashCode() can lead to unexpected results.
Rule |
Severity |
Likelihood |
Remediation Cost |
Priority |
Level |
|---|---|---|---|---|---|
MET31-J |
low |
unlikely |
high |
P1 |
L3 |
TODO
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
\[[API 06|AA. Java References#API 06]\] [Class Object|http://java.sun.com/javase/6/docs/api/java/lang/Object.html]
\[[Bloch 08|AA. Java References#Bloch 08]\] Item 9: Always override {{hashCode}} when you override {{equals}}
\[[MITRE 09|AA. Java References#MITRE 09]\] [CWE ID 581|http://cwe.mitre.org/data/definitions/581.html] "Object Model Violation: Just One of Equals and Hashcode Defined" |
MET30-J. Follow the general contract while overriding the equals method 09. Methods (MET) MET32-J. Ensure that constructors do not call overridable methods