22 · 32 · 5 min
The same object, or an equal one
Two questions that look the same and are not: is this the same object, and does this represent the same value.
String a = new String("hello");
String b = new String("hello");
a == b // false: two objects
a.equals(b) // true: same value== compares references. equals compares whatever the class decides.
Why this bites beginners
Integer x = 127, y = 127;
x == y // true
Integer p = 128, q = 128;
p == q // falseJava caches boxed integers from -128 to 127, so small values are the same object and larger ones are not. The lesson is not to memorise the range; it is to never use == on boxed types or strings.
Python has the same trap with is against ==, for the same reason.
The contract
If you override equals, you must override hashCode, and the rule is:
equal objects must have equal hash codes
Breaking it does not throw. It makes the object unfindable in any hashed collection, which is a silent and confusing failure.
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Point p)) return false;
return x == p.x && y == p.y;
}
@Override
public int hashCode() {
return Objects.hash(x, y);
}equals must also be reflexive, symmetric, transitive, consistent, and false for null. Symmetry is the one that breaks in practice, usually when a subclass compares against a superclass and the superclass does not agree.
Records and data classes
public record Point(int x, int y) { }equals, hashCode and toString are generated from the fields, and the fields are final. Python's @dataclass, Kotlin's data class and C# records do the same. For a value type this is the right default, and it removes a recurring source of bugs.
Value or identity
The deeper question is which kind of thing you are modelling.
| Value | Entity | |
|---|---|---|
| Equal when | fields match | identifiers match |
| Example | money, a date, a coordinate | a customer, an order |
| Should be immutable | yes | usually not |
Two customers with the same name are not the same customer. Two Money(100, INR) are interchangeable. Getting this wrong means either duplicate records or two objects the system refuses to treat as equal.