hashCode() Fundamentals
The hashCode() method returns an integer value that serves as a unique identifier for an object. When not overridden, it uses Object's native implementation which computes a hash based on the object's memory address, not its content.
Common clases like String and Integer override hashCode() to ensure objects with identical content produce the same hash value:
// String hashCode implementation
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
HashCode Contract Requirements
The hashCode() method must adhere to three key principles:
- Consistency: hashCode() must return the same value for unchanged objects during program execution
- Equality correlation: Objects that are equal according to equals() must have idnetical hash codes
- Collision handling: Unequal objects should ideally have different hash codes for optimal hash table performance
Overriding equals() and hashCode()
Proper implementation is crucial for collection operations. Here's a sample implementation pattern:
class Student {
private boolean enrolled;
private int studentAge;
private String studentName;
private Integer studentGrade;
@Override
public boolean equals(Object comparisonTarget) {
if (this == comparisonTarget) return true;
if (comparisonTarget == null || getClass() != comparisonTarget.getClass()) return false;
Student student = (Student) comparisonTarget;
return enrolled == student.enrolled &&
studentAge == student.studentAge &&
Objects.equals(studentName, student.studentName) &&
Objects.equals(studentGrade, student.studentGrade);
}
@Override
public int hashCode() {
return Objects.hash(enrolled, studentAge, studentName, studentGrade);
}
}
Floating-Point Precision Challenges
When working with geometric calculations involving floating-point numbers, precision issues can cause unexpected behavior in hashCode() implementations:
class GeometricLine {
private int startX;
private int endX;
private int startY;
private int endY;
public GeometricLine(int startX, int endX, int startY, int endY) {
this.startX = startX;
this.endX = endX;
this.startY = startY;
this.endY = endY;
}
public double calculateSlope() {
if (startX == endX) return Double.MAX_VALUE;
if (startY == endY) return 0.0;
return (startY - endY + 0.0) / (startX - endX + 0.0);
}
public double calculateIntercept() {
if (startX == endX) return startX;
if (startY == endY) return startY;
double intercept = startY + 0.0 - (startX + 0.0) * calculateSlope();
return intercept == 0.0 ? 0.0 : intercept;
}
}
Positive and Negative Zero Issue
Double values distinguish between +0.0 and -0.0, wich have different hash codes:
Double positiveZero = (3.0 - 3.0) / (2.0 - 1.0); // +0.0
Double negativeZero = (3.0 - 3.0) / (1.0 - 2.0); // -0.0
System.out.println(positiveZero.hashCode()); // Different from
System.out.println(negativeZero.hashCode()); // negative zero hash
This distinction causes identical mathematical lines to be treated as different objects in hash-based collections.
Floating-Point Precision Solutions
To handle precision issues in geometric calculations:
@Override
public boolean equals(Object comparisonTarget) {
if (this == comparisonTarget) return true;
if (!(comparisonTarget instanceof GeometricLine)) return false;
GeometricLine line = (GeometricLine) comparisonTarget;
int slopeComparison = BigDecimal.valueOf(calculateSlope())
.setScale(6, RoundingMode.HALF_EVEN)
.compareTo(BigDecimal.valueOf(line.calculateSlope())
.setScale(6, RoundingMode.HALF_EVEN));
int interceptComparison = BigDecimal.valueOf(calculateIntercept())
.setScale(6, RoundingMode.HALF_EVEN)
.compareTo(BigDecimal.valueOf(line.calculateIntercept())
.setScale(6, RoundingMode.HALF_EVEN));
return slopeComparison == 0 && interceptComparison == 0;
}
@Override
public int hashCode() {
return Objects.hash(
BigDecimal.valueOf(calculateSlope()).setScale(6, RoundingMode.HALF_EVEN),
BigDecimal.valueOf(calculateIntercept()).setScale(6, RoundingMode.HALF_EVEN));
}