According to the Classes that override the Object.equals()
method must also override the Object.hashCode()
method. The java.lang.Object
specification:"If class requires that any two objects are that compare equal according to using the equals(Object)
method , then calling must produce the same integer result when the hashCode()
method is invoked on each of the two objects must produce the same integer result."objects [API 2014].
The equals()
method is used to determine logical equivalence between object instances. Consequently, the hashCode()
method must return the same value for all equivalent objects. Failure to follow this contract is a common source of common bugsdefects.
Noncompliant Code Example
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 code example stores a associates credit card number into numbers with strings using a HashMap
and retrieves itsubsequently attempts to retrieve the string value associated with a credit card number. 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 4111111111111111
; the actual retrieved value is null
.
Code Block | ||
---|---|---|
| ||
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) { MapMap<CreditCard, String> m = new HashMap<CreditCard, HashMapString>(); m.put(new CreditCard(100), "Java4111111111111111"); System.out.println(m.get(new CreditCard(100))); } } |
The cause of this erroneous behavior is that the CreditCard
class overrides the equals()
method but fails to override the hashCode()
method. Consequently, the default hashCode()
method returns a different value for each object, even though the objects are logically equivalent; these differing values lead to examination of different buckets in the hash table, which prevents the get()
method from finding the intended value.
Compliant Solution
Note that by specifying the credit card number in main()
, these code examples violate MSC03-J. Never hard code sensitive information for the sake of brevity.
Compliant Solution
This compliant solution overrides the hashCode()
method so that it generates the same value for any two instances that are considered to be equal by the equals()
method. Bloch discusses the recipe to generate such a hash function in detail [Bloch 2008] This compliant solution shows how {{hashCode}} can be overridden so that the same value is generated for an instance. The recipe to generate such a hash function is described in \[[Bloch 08|AA. Java References#Bloch 08]\] Item 9: Always override {{hashCode}} when you override {{equals}}. Wiki Markup
Code Block | ||
---|---|---|
| ||
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 = 717; result = 31 37* result + number; return result; } public static void main(String[] args) { Map<CreditCard, MapString> m = new HashMap<CreditCard, HashMapString>(); m.put(new CreditCard(100), "Java4111111111111111"); System.out.println(m.get(new CreditCard(100))); } } |
Risk Assessment
Overriding the method equals()
method without correspondlingly overriding the method hashCode()
method can lead to unexpected results.
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|
MET09-J |
Low |
Unlikely |
High | P1 | L3 |
Automated Detection
TODO
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
References
Wiki Markup |
---|
\[[Bloch 08|AA. Java References#Bloch 08]\] Item 9: Always override {{hashCode}} when you override {{equals}}
\[[API 06|AA. Java References#API 06]\] [Class Object|http://java.sun.com/j2se/1.4.2/docs/api/java/lang/Object.html] |
Automated detection of classes that override only one of equals()
and hashcode()
is straightforward. Sound static determination that the implementations of equals()
and hashcode()
are mutually consistent is not feasible in the general case, although heuristic techniques may be useful.
Tool | Version | Checker | Description | ||||||
---|---|---|---|---|---|---|---|---|---|
CodeSonar |
| JAVA.IDEF.EQUALSNOHC | Defines equals but not hashCode (Java) | ||||||
Parasoft Jtest |
| CERT.MET09.OVERRIDE | Override 'Object.hashCode()' when you override 'Object.equals()' and vice versa | ||||||
PVS-Studio |
| V6049 | |||||||
SonarQube |
| "equals(Object obj)" and "hashCode()" should be overridden in pairs |
Related Guidelines
Bibliography
[API 2014] | |
Item 9, "Always Override |
...
MET30-J. Follow the general contract while overriding the equals method 09. Methods (MET) MET32-J. Ensure that constructors do not call overridable methods