Object, Gleichheit & Unveränderlichkeit
equals und hashCode richtig gemacht, toString und warum Unveränderlichkeit das Leben erleichtert.
Deutsche Übersetzung in Arbeit
Diese Lektion ist noch nicht ins Deutsche übersetzt und wird daher auf Englisch angezeigt. Der Rest der Seite ist vollständig lokalisiert.
Auf dieser Seite
"Are these two things equal?" sounds simple, but it's the source of countless
Java bugs. This lesson demystifies equals(), hashCode(), toString(), and
why immutability makes objects easier to reason about.
== vs. equals(): identity vs. value
There are two very different questions you can ask about two objects:
==asks: "Are these the exact same object in memory?" (identity).equals()asks: "Do these objects have the same value?" (equality)
Identical twins look the same (equal by value) but are two different people
(different identity). == checks "is this literally the same person?";
.equals() checks "do they look/count as the same?". For most real comparisons,
you want .equals().
String a = new String("hi");
String b = new String("hi");
System.out.println(a == b); // false - two different objects
System.out.println(a.equals(b)); // true - same text valueNever compare Strings (or objects) with ==
Using == on Strings compares memory identity, not text - a classic bug that
sometimes works by accident (due to Java's String pool) and then fails
mysteriously. Always use .equals() to compare object values.
Overriding equals() and hashCode()
By default, equals() on your own classes behaves like == (identity). If you
want two objects with the same data to count as equal, you must override
equals() - and whenever you do, you must also override hashCode().
public class Point {
private final int x, y;
public Point(int x, int y) { this.x = x; this.y = y; }
@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);
}
}The equals/hashCode contract
The rule: if two objects are equal, they must have the same hash code.
A coat-check assigns each coat a number so it can be found fast. If two identical
coats got different numbers, the attendant would lose one. Hash-based
collections (HashMap, HashSet) use hashCode() to find objects quickly - so
equal objects must share a hash code, or the collection breaks.
Break the contract, break your collections
If you override equals() but forget hashCode(), objects you consider equal
may land in different buckets of a HashSet or HashMap - so lookups silently
fail and duplicates sneak in. Always override them together.
Let records do it for you
Remember records from the last lesson? They generate a correct equals(),
hashCode(), and toString() automatically. For simple data classes, record Point(int x, int y) {} gives you all of this for free - no chance of getting the
contract wrong.
toString(): a readable description
By default, printing an object shows something ugly like Point@1b6d3586.
Override toString() to give a helpful description:
@Override
public String toString() {
return "Point(" + x + ", " + y + ")";
}System.out.println(new Point(3, 4)); // Point(3, 4)This makes debugging and logging vastly easier.
Immutability: objects that never change
An immutable object can't be modified after creation. You saw this with Strings and records. To make your own class immutable:
- Make fields
private final. - Set them only in the constructor.
- Provide no setters.
public final class Money {
private final long cents;
private final String currency;
public Money(long cents, String currency) {
this.cents = cents;
this.currency = currency;
}
// getters only - no setters
public Money add(Money other) {
return new Money(cents + other.cents, currency); // returns a NEW object
}
}Why favor immutability?
Immutable objects are simpler to reason about (their value never changes), safe
to share freely, safe across threads without locks, and safe to use as keys in a
HashMap. Whenever a class is really just a value, prefer making it immutable -
your future self will thank you.
Quick check
You override equals() so two Points with the same coordinates are equal. What else MUST you override?
Key takeaways
- == compares object identity (same object in memory); .equals() compares value - usually you want .equals().
- Never compare Strings or objects with ==; use .equals().
- Override equals() for value equality, and ALWAYS override hashCode() alongside it.
- Equal objects must have equal hash codes, or HashSet/HashMap will misbehave.
- Override toString() for readable output; records generate all three for free.
- Immutable objects (private final fields, no setters) are simpler, thread-safe, and safe to share.
Congratulations - you've completed Stage 2! You now think in objects: classes, the four pillars, interfaces, modern types, and equality. Stage 3 puts these skills to work with exceptions, collections, generics, and files.