Where the Problem Started
Identity and equality are easy to rush past when learning Java, but real bugs often appear exactly where that distinction is missed. Whether two references point to the same instance and whether two objects should be treated as the same value are different questions. This post separates the responsibilities of ==, equals, and hashCode.
Identity and equality are concepts that are often confused. In Java, the former is the concept behind the (‘==’) operator, and the latter is the concept behind the (equals()) method.
Identity
In Java, every object is accessed by reference. When using the == operator, comparison happens against the reference itself, not the actual values on the left and right.
The exception is primitive types (int, char, float …), where the actual values are compared.
Accessing by reference means that even if the actual values are the same, if the addresses are different, the ‘==’ operation returns false.
Implementation Path
Integer x = 100;
Integer y = 100;
System.out.println(x == y);
This code would print false. However, because of JVM integer caching, values from -128 to 127 are printed as true.
Equality
The equals() method checks logical equivalence. It means checking the data itself inside the two objects.
It is also possible to override this method in a class. That means you can define what equality means in the way you intend.
In that case, it is good to override hashCode() as well. Java Collections such as HashSet and HashMap exist, and both methods need to be used for them to work correctly.
public class Person {
private String name;
private int age;
// Constructor
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// Override equals() method
@Override
public boolean equals(Object obj) {
// Check if the object is compared with itself
if (this == obj) {
return true;
}
// Check if obj is an instance of Person
if (obj == null || obj.getClass() != this.getClass()) {
return false;
}
// Typecast obj to Person so that we can compare data members
Person person = (Person) obj;
// Compare the data members and return accordingly
return name.equals(person.name) && age == person.age;
}
// Override hashCode() method
@Override
public int hashCode() {
// Use a prime number to calculate the hash code
int result = 17;
result = 31 * result + name.hashCode();
result = 31 * result + age;
return result;
}
// Getters and setters for name and age
// ...
}
Implementation Takeaway
Object comparison is not a small syntax detail; it directly affects collection behavior and domain modeling. With hash-based structures such as HashSet and HashMap, the equals and hashCode contract must be honored together. The more value objects a system has, the more important this basic rule becomes.
When implementing equals(), it is important to make it null-safe, meaning exception handling for null arguments.
Conclusion:
- Use ‘==’ for primitive data types.
- Use equals() for object comparison.
- Override equals() and hashCode() together.
- Do not use ‘==’ with String. (Because String is an object.)
- Always do a null check when overriding equals().