Equals, hashCode and toString
Three methods that describe an object
The equals(), hashCode(),
and toString() methods are defined in Object,
so every object has them. But the behavior inherited from Object
is often too general for our own classes, so these methods are commonly overridden.
Each method has its own job: equals() answers the question
“should these two objects be considered equal?”, hashCode()
provides a numeric hash consistent with that equality, and toString()
creates a textual representation of the object.
equals(): equality by meaning
If a class does not override equals(), the implementation
from Object is used. It considers two objects equal only when
both references point to the very same object — essentially the same rule as the
== operator uses for references.
Point first = new Point(2, 3);
Point second = new Point(2, 3);
System.out.println(first == second); // false
System.out.println(first.equals(second)); // false
But for a Point class, it may make sense to consider two points
equal when their coordinates match. In that case, this rule must be expressed explicitly
in equals().
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Point other = (Point) obj;
return x == other.x && y == other.y;
}
First, the simplest case is handled — comparing an object with itself.
Then null and an object of another class are rejected,
and finally the fields that define equality for points are compared.
A correct equals() implementation must follow several rules:
- an object is equal to itself;
- if
a.equals(b)is true, thenb.equals(a)must also be true; - if
aequalsb, andbequalsc, thenamust equalc; - the result must not change on its own while the data used in the comparison remains unchanged;
- any non-null object is not equal to
null.
hashCode(): equal objects must stay consistent
hashCode() returns an integer — the object's hash.
The main rule here is connected to equals():
if two objects are equal according to equals(), their
hashCode() methods must return the same value.
The reverse is not true: the same hash does not mean that two objects are equal.
Different objects can have the same hashCode().
@Override
public int hashCode() {
return java.util.Objects.hash(x, y);
}
The hash is calculated from the same x and
y data used by equals().
This guarantees that two equal objects receive the same hash.
equals(), override hashCode() too.
Otherwise, two objects may be considered equal while having inconsistent hashes.
That breaks the contract of these methods and leads to incorrect behavior in code
that relies on them being consistent.
toString(): an object in a readable form
toString() returns a string representation of an object.
The implementation from Object usually produces a string such as
Point@1a2b3c. It can serve as a technical representation,
but it says almost nothing about the object's contents.
That is why toString() is often overridden in custom classes
to include fields that are actually useful:
@Override
public String toString() {
return "Point{x=" + x + ", y=" + y + "}";
}
Now printing the object immediately shows its state:
Point point = new Point(2, 3);
System.out.println(point); // Point{x=2, y=3}
There is no need to call point.toString() explicitly here:
when an object is passed to System.out.println(),
its string representation is used automatically.
A toString() format should be concise and easy for a person to read.
The standard part after @ should not be treated
as the object's memory address or as a stable identifier.