Inheritance and Polymorphism · Lesson 25 of 42
The Object Class: equals, hashCode, toString
Every Java class extends java.lang.Object, and inherits its methods. Three of them you will override in almost every data class: toString(), equals() and hashCode(). Getting the last two wrong causes some of the most confusing bugs in Java – objects that "disappear" from a HashSet or HashMap.
In this lesson
- The methods every object inherits
- Overriding
toString() equals(): the contract and a correct implementation- Why
hashCode()must agree withequals()
What Object gives you
| Method | Default behaviour |
|---|---|
toString() | ClassName@hexHash – not useful for people |
equals(Object o) | same as ==: true only for the same object |
hashCode() | a number derived from object identity |
getClass() | the runtime class |
wait(), notify(), notifyAll() | low-level thread coordination |
clone(), finalize() | legacy – rarely used today |
The problem with the defaults
import java.util.HashSet;
import java.util.Set;
public class Main {
public static void main(String[] args) {
Point a = new Point(2, 3);
Point b = new Point(2, 3);
System.out.println(a.equals(b)); // false: different objects
Set<Point> set = new HashSet<>();
set.add(a);
set.add(b);
System.out.println(set.size()); // 2 "duplicates"
System.out.println(a.toString().startsWith("Point@"));
}
}
class Point {
final int x, y;
Point(int x, int y) { this.x = x; this.y = y; }
}
false 2 true
Two points with the same coordinates should be equal. For that we override all three methods.
A correct implementation
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
public class Main {
public static void main(String[] args) {
Point a = new Point(2, 3), b = new Point(2, 3);
System.out.println(a.equals(b) + " " + (a.hashCode() == b.hashCode()));
Set<Point> set = new HashSet<>(Set.of(a));
System.out.println(set.contains(b) + " " + set.size());
Map<Point, String> labels = new HashMap<>();
labels.put(a, "start");
System.out.println(labels.get(new Point(2, 3)));
System.out.println(a);
}
}
class Point {
private final int x, y;
Point(int x, int y) { this.x = x; this.y = y; }
@Override
public boolean equals(Object o) {
if (this == o) return true; // same object
if (!(o instanceof Point other)) return false; // null or another type
return x == other.x && y == other.y; // compare the fields that matter
}
@Override
public int hashCode() {
return Objects.hash(x, y); // same fields as equals
}
@Override
public String toString() {
return "Point(" + x + ", " + y + ")";
}
}
true true true 1 start Point(2, 3)
The equals contract
The Java documentation requires equals to be:
- Reflexive:
a.equals(a)is true. - Symmetric: if
a.equals(b)thenb.equals(a). - Transitive: if a equals b and b equals c, then a equals c.
- Consistent: repeated calls give the same answer while the objects don't change.
a.equals(null)is always false.
Why hashCode must match equals
Hash-based collections first use hashCode() to pick a bucket, then use equals() to compare only inside that bucket.
set.contains(new Point(2, 3))
- hashCode()→ bucket number
- Go to that bucketonly a few objects are there
- equals() each onetrue → found
If equal objects produced different hash codes, Java would look in the wrong bucket and never find the match:
import java.util.HashSet;
import java.util.Set;
public class Main {
public static void main(String[] args) {
Set<Badge> set = new HashSet<>();
set.add(new Badge("JAVA"));
System.out.println(new Badge("JAVA").equals(new Badge("JAVA")));
System.out.println(set.contains(new Badge("JAVA"))); // almost always false!
}
}
class Badge {
final String code;
Badge(String code) { this.code = code; }
@Override
public boolean equals(Object o) {
return o instanceof Badge b && code.equals(b.code);
}
// hashCode() NOT overridden – breaks hash collections
}
true false
The rule
If two objects are equal according to equals(), they must have the same hashCode(). (The reverse isn't required: different objects may share a hash code – that's a collision.) Override both together, using the same fields – IDEs generate them, and records (chapter 10) do it automatically.
Common mistake: mutable keys
If you change a field used in hashCode() after putting the object in a HashSet or using it as a HashMap key, it is now in the wrong bucket and effectively lost. Use immutable objects as keys.
Helpful utilities
import java.util.Objects;
public class Main {
public static void main(String[] args) {
String a = null, b = "java";
System.out.println(Objects.equals(a, b)); // null-safe
System.out.println(Objects.equals(null, null));
System.out.println(Objects.hash("Asha", 21) == Objects.hash("Asha", 21));
System.out.println(Objects.requireNonNullElse(a, "default"));
}
}
false true true default
Practice
Try it yourself
Write a
Studentclass whose equality is based only onrollNo, and show that a HashSet keeps one copy.Show solution
import java.util.HashSet; import java.util.Set; public class Main { public static void main(String[] args) { Set<Student> set = new HashSet<>(); set.add(new Student(21, "Ira")); set.add(new Student(21, "Ira K.")); System.out.println(set.size()); } } class Student { final int rollNo; final String name; Student(int rollNo, String name) { this.rollNo = rollNo; this.name = name; } @Override public boolean equals(Object o) { return o instanceof Student s && s.rollNo == rollNo; } @Override public int hashCode() { return Integer.hashCode(rollNo); } }1
Quick check
What does the default equals() compare?
Object identity – the same as ==.
If you override equals but not hashCode, what breaks?
Hash-based collections (HashSet, HashMap) can't find equal objects because they look in different buckets.
Can two unequal objects have the same hashCode?
Yes – that's a collision, and it's allowed.
Summary
- Every class inherits from Object; override
toString()for readable output. - Override
equals()with the contract in mind, and always overridehashCode()with the same fields. - Use
Objects.equalsandObjects.hash; keep hash keys immutable.
Interview practice: Java OOP in depth.
Finished this lesson?
Mark it complete to track your progress, then carry on with the next lesson.