Loslegen
Javaneer
Zurück zur Stufe
Stufe 2·Objektorientierte Programmierung

Object, Gleichheit & Unveränderlichkeit

equals und hashCode richtig gemacht, toString und warum Unveränderlichkeit das Leben erleichtert.

16 Min. LesezeitFortgeschritten

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)
Twins vs. the same person

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 value

Never 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.

hashCode is like a coat-check number

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:

  1. Make fields private final.
  2. Set them only in the constructor.
  3. 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.