Why Do We Need to Override equals() in Java? (equals-hashCode part 1)
Photo by Volodymyr Dobrovolskyy on Unsplash
Why Do We Need to Override equals() in Java? (equals-hashCode part 1)

Photo by Volodymyr Dobrovolskyy on Unsplash
In this article, we’ll discuss the implementation equals() method in Java. Key points we’ll dive into:
- How the default
equals()behaves and why it should be overridden. - Type-checking with
equals(). - The
equals()contract. - Tips when overriding
equals().
The equals() method
The equals() method is used compare two objects to determine if they’re equal. This method is provided by default in Java’s Object class:
public boolean equals(Object obj) {
return (this == obj);
}
Why does the default equals() method of Object return false when two objects have the same value?
When comparing objects using the predefined equals() in the Object class, the method returns false.
If you look at the method’s implementation given above, it simply compares references of the objects. It does not take into consideration what’s inside the objects — it only checks if both references point to the same object in memory. Therefore, it behaves the same as comparison by == operator.
The result is the same for any object comparison since every class in Java implicitly extends the Object class:
public class Device {
private String name;
public Device(String name) {
this.name = name;
}
}
public class Main {
public static void main(String[] args) {
Device laptop = new Device("Apple");
Device phone = new Device("Apple");
System.out.println(laptop.equals(phone)); // false
}
}
Even though laptop and phone have the same value of name, they’re pointing to two different objects.
When does it return true?
It can return true if both references point to the same object:
public class Main {
public static void main(String[] args) {
Device phone = new Device("Apple");
Device smartPhone = phone;
System.out.println(phone.equals(smartPhone)); // true
}
}
But why does the default implementation of equals() only compare references?
This should be obvious, because at Object’s level — that is, the root Object — Java does not know what makes objects “equal”. It has no idea what an object contains.
It only knows that each object has a memory address, so it checks if two references point to the same memory location. (this == obj).
This is the only equality that the Objectclass can guarantee. Anything more specific must be defined by the class itself by overriding the equals() method.
Overriding equals()
To compare the contents of objects, the equals()method has to be overridden:
public class Device {
private String name;
public Device(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true; // compares references
if (o == null || getClass() != o.getClass()) return false;
Device device = (Device) o;
return Objects.equals(name, device.name); // compares contents
}
@Override
public int hashCode() {
return Objects.hashCode(name);
}
}
In our implementation, we’re first comparing by references, then we’re performing type checking — if (o == null || getClass() != o.getClass() return false;— before finally comparing the contents of laptopand phone, which results in true.
Notice that we’ve also overridden
hashCode()method. We’ll discuss why we need it in a future article.
What’s the purpose of type-checking in equals()?
Type checking in equals() simply refers to a way to ensure both compared objects are of the same type, because the input o can be any object, so we need to make sure we’re passing the object of the right type, or else a ClassCastException could occur.
This is achieved by checking what type o actually is using either:
getClass():
if (o == null || getClass() != o.getClass() return false;
This method returns true if both objects are of the exact same class. This is a strict check and more common. Subclasses are not allowed.
instanceof:
if (!(o instanceof Device)) return false;
It returns true if the object is of the current class or any of its subclasses. It’s more flexible but can cause problems if subclasses override equals(), as this can easily break the symmetry rule of the equals() contract.
The symmetry rule is one of the key rules of equals() contract defined by the Object class. It essentially states that equality must be mutual.
— if A is equals to B, then B should be equal A.
— This rule is often violated when comparing a superclass to a subclass that introduces additional fields. To solve this we can simply make equals() method final to ensure that no subclass overrides it and violates the symmetry rule.
Stringclass is an example of a class that has its own implementation of theequals()method.
The equals() contract
Let’s dive deeper into important aspects of equals() contract. The contract specified in Java for this method essentially states that the equals() method must satisfy the following rules:
- Reflexive:
x.equals(x)must return true. - Symmetric: if
x.equals(y)istruetheny.equals(x)must also betrue. - Transitive: for
x,yandz, ifx.equals(y)andy.equals(z)istrue, thenx.equals(z)must also betrue. - Consistent: multiple invocations of
x.equals(y)must return the same result, unless object data changes. - non-null:
x.equals(null)must always returnfalse.
Common Mistake: Missing Null Check in equals()
A common mistake when implementing equals() method is forgetting to handle null:
@Override
public boolean equals(Object o) {
if (this == o) return true;
Device device = (Device) o; // ❌ Throws NullPointerException if o is null
return Objects.equals(name, device.name);
}
This causes a NullPointerException when equals() is called with a null argument:
public static void main(String[] args) {
Device phone = new Device("Apple");
Device anotherDevice = null;
System.out.println(phone.equals(anotherDevice)); // throws NullPointerException
}
Fix: Add a null and type check before casting:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false; // this prevents NPE
Device device = (Device) o;
return Objects.equals(name, device.name);
}
By checking o == null, the method safely returns false instead of throwing an exception.
Tip:
When to use Objects.equals(a, b) instead of a.equals(b):
- Use
Objects.equals(a, b)when either side can be null, because it handlesNullPointerException. - Use
a.equals(b)only when you’re certain thatais notnull.
Device a = null;
Device b = new Device();
System.out.println(Objects.equals(a, b)); // false
System.out.println(a.equals(b)); // NullPointerException
Tip:
When Objects.equals() Can Still Throw a NullPointerException:
Objects.equals() does not throw a NullPointerExceptionon its own, but it may propagate one thrown by the overridden equals() method of the first non-null argument does.
But if
Objects.equals()belongs to theObjectsclass, why does our customequals()in our class affects itsnullsafety?
If you look at Objects.equals() implementation:
public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));
}
Notice that it’s internally invoking a.equals(b) when a is not null.
So if a is an instance of our class Device then it’s actually calling our overridden equals() method.
Therefore if our equals() implementation does not handle null safely, then Objects.equals(a, b)will throw an NPE.
In short, Objects.equals() is only as safe as our equals() implementation — it can still throw an exception if that implementation itself doesn’t handle null safely.
In summary, the default equals() only compares object references, not their internal state, which is why two distinct objects with identical values are considered unequal by default. To ensure logical equality, classes must override equals() and explicitly specify what makes two instances equal.
When overriding equals() , it’s important to:
- perform proper reference, null and type checks.
- follow the
equals()contract to ensure predictability and correct behavior. - be careful with type-checking strategies (
getClass()vs.instanceof) to prevent contract violations.
In a future article, we’ll look into the contract between equals() and hashCode() as it’s closely related to the topic of this article.
Thanks for reading!
메타데이터
- post_id
- cfb7fa180b5a
- slug
- why-do-we-need-to-override-equals-in-java-equals-hashcode-part-1-cfb7fa180b5a
- url
- https://medium.com/javarevisited/why-do-we-need-to-override-equals-in-java-equals-hashcode-part-1-cfb7fa180b5a
- canonical_url
- https://medium.com/javarevisited/why-do-we-need-to-override-equals-in-java-equals-hashcode-part-1-cfb7fa180b5a
- author_url
- https://medium.com/@ferasama
- status
- ok
- fetched_at
- 2026-06-20 20:29:01