CodeGym /Kurse /JAVA 25 SELF /Immutability – Unveränderlichkeit von Record-Klassen

Immutability – Unveränderlichkeit von Record-Klassen

JAVA 25 SELF
Level 22, Lektion 1
Verfügbar

1. Record-Klassen und Unveränderlichkeit

Unveränderlichkeit ist eine Eigenschaft eines Objekts, bei der sein Zustand nach der Erstellung nicht mehr geändert werden kann. Anders ausgedrückt: Ist das Objekt einmal erstellt, können Sie seine internen Daten nicht mehr ändern. Punkt! Wie in Stein gemeißelt.

Stellen Sie sich ein Zugticket vor. Sobald es gedruckt ist, können Sie Datum oder Abfahrtsort nicht mehr ändern (es sei denn, man rechnet mit pfiffigen Photoshop-Spielereien). Ein Ticket ist ein unveränderliches Objekt. Wenn Sie ein anderes Ticket wollen – kaufen Sie ein neues.

In der Programmierung nennt man solche Objekte unveränderliche Objekte. Sie schützen das Programm vor unbeabsichtigten Änderungen und machen den Code sicherer und vorhersehbarer.

Merkmale eines unveränderlichen Objekts

  • Alle Felder des Objekts sind final (sie können nur einmal zugewiesen werden, gewöhnlich im Konstruktor).
  • Es gibt keine Setter (Methoden, die Feldwerte ändern).
  • Alle Methoden, die interne Daten zurückgeben, liefern entweder Kopien zurück oder die Daten selbst sind ebenfalls unveränderlich.

Record-Klassen in Java wurden genau dafür erfunden, unveränderliche Datenstrukturen einfach und ohne Schmerzen zu erstellen.

Warum ist ein Record unveränderlich?

  • Alle Komponenten eines Records sind final.
    Wenn Sie einen record deklarieren, macht der Compiler automatisch alle seine Felder private und final. Das bedeutet: Nach der Erstellung eines Objekts können Sie seine Felder nicht mehr ändern.
  • Keine Setter.
    In einer Record-Klasse können Sie keine Methode setX(int x) hinzufügen – der Compiler lässt es nicht zu, den Feldwert nach der Objekterstellung zu ändern.
  • Der Konstruktor weist Werte nur einmal zu.
    Alle Werte werden ausschließlich beim Erzeugen des Objekts gesetzt.

Beispiel

public record Point(int x, int y) {}

Point p = new Point(5, 10);
// p.x = 7; // Kompilierungsfehler: Feld x ist private und final
// p.x(7);  // Fehler: kein Setter vorhanden!
System.out.println(p.x()); // 5

Der Versuch, ein Feld zu ändern oder einen nicht vorhandenen Setter aufzurufen, führt zu einem Kompilierungsfehler. Java achtet streng auf den Unveränderlichkeitsvertrag.

2. Vorteile unveränderlicher Objekte

Sicherheit in der Nebenläufigkeit

In nebenläufigen Programmen (und das sind heute die meisten!) sind unveränderliche Objekte wie eine schusssichere Weste. Wenn ein Objekt nicht geändert werden kann, können verschiedene Threads es gefahrlos lesen, ohne befürchten zu müssen, dass jemand in diesem Moment die Daten verändert. Man muss den Zugriff nicht synchronisieren und muss sich nicht um Datenrennen sorgen.

Fakt: Viele Klassen in der Standardbibliothek von Java, die in nebenläufigen Szenarien intensiv genutzt werden, sind entweder unveränderlich oder speziell gegen Änderungen geschützt.

Leichtere Verständlichkeit des Codes

Wenn ein Objekt unveränderlich ist, können Sie sicher sein: Sie geben es an eine andere Methode oder Klasse weiter – und es ändert sich nicht „hinter Ihrem Rücken“. Das erleichtert das Lesen und Debuggen des Codes erheblich. Man muss nicht rätseln, wer wo ein Feld geändert haben könnte – niemand konnte es ändern!

Gut als Schlüssel in Collections geeignet

Unveränderliche Objekte eignen sich hervorragend als Schlüssel in Collections wie HashMap oder HashSet. Warum? Weil ihre equals- und hashCode-Methoden nur von Feldern abhängen, die sich nicht ändern. Das heißt, ein Objekt „geht“ in der Collection nicht „verloren“, weil sich sein Zustand geändert hat.

Weniger versteckte Fehler

Ein veränderliches Objekt lässt sich leicht verderben, wenn man eine Referenz versehentlich an die falsche Stelle weitergibt. Ein unveränderliches Objekt hingegen ist wie ein gedrucktes Buch: Niemand kann eine Seite herausreißen oder überschreiben.

3. Vergleich mit normalen Klassen

Vergleichen wir das Verhalten einer normalen Klasse mit dem einer Record-Klasse. Als Beispiel nehmen wir ein einfaches Modell eines Punktes in der Ebene.

Normale Klasse (mutable)

public class PointClass {
    private int x;
    private int y;

    public PointClass(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() { 
        return x; 
    }
    public int getY() { 
        return y; 
    }

    public void setX(int x) { 
        this.x = x; 
    }
    public void setY(int y) { 
        this.y = y; 
    }
}

Man kann ein Objekt erstellen und seinen Zustand anschließend nach Belieben ändern:

PointClass p = new PointClass(1, 2);
p.setX(10); // p.x ist jetzt 10

Record-Klasse (immutable)

public record Point(int x, int y) {}

Sie erstellen ein Objekt – und das war’s, es bleibt für immer so, wie es erstellt wurde:

Point p = new Point(1, 2);
// p.x = 10;   // Fehler! Kein Zugriff auf das Feld
// p.x(10);    // Fehler! Kein Setter

Tabelle: Verhaltensvergleich

Normale Klasse Record-Klasse
Felder beliebig nur private final
Setter können hinzugefügt werden können nicht hinzugefügt werden
Veränderlichkeit veränderlich (mutable) unveränderlich
Automatische Generierung nein ja (equals, hashCode, toString)

4. Praxis: Unveränderlichkeit von Record-Klassen nutzen

Probieren wir aus, wie man eine Record-Klasse in einem kleinen Beispiel verwendet. Angenommen, wir haben eine Banking-Anwendung und möchten Informationen über eine Transaktion speichern:

public record Transaction(String fromAccount, String toAccount, double amount) {}

Objekt erzeugen:

Transaction t = new Transaction("12345", "67890", 1500.0);
System.out.println(t);
// Transaction[fromAccount=12345, toAccount=67890, amount=1500.0]

Versuchen wir, Geld auf ein anderes Konto zu „überweisen“:

// t.toAccount = "11111"; // Fehler! Feld ist final, kein Zugriff
// t.toAccount("11111");  // Fehler! Kein Setter

Wenn wir eine andere Transaktion brauchen – erzeugen wir ein neues Objekt:

Transaction t2 = new Transaction(t.fromAccount(), "11111", t.amount());

Wichtig: Unveränderlichkeit heißt nicht „unpraktisch“. Es ist nur ein anderer Arbeitsstil: Wenn Sie einen neuen Zustand brauchen – erzeugen Sie ein neues Objekt.

5. Besonderheiten der Unveränderlichkeit: Was man beachten sollte

Unveränderlichkeit ist nicht immer absolut!

Eine Record-Klasse garantiert, dass ihre Felder sich nicht ändern. Wenn ein Feld jedoch eine Referenz auf ein veränderliches Objekt ist (z. B. ein Array oder eine normale Klasse), kann der Inhalt dieses Objekts sehr wohl verändert werden.

Beispiel mit einem Array

public record DataHolder(int[] data) {}

int[] arr = {1, 2, 3};
DataHolder holder = new DataHolder(arr);
arr[0] = 99;
System.out.println(holder.data()[0]); // 99! Das Array wurde verändert

Schlussfolgerung: Wenn Sie echte Unveränderlichkeit wollen, verwenden Sie nur unveränderliche Typen (String, Integer, andere records usw.) oder erstellen Sie Kopien veränderlicher Objekte im kanonischen Konstruktor der Record-Klasse. Zum Beispiel:

int[] copy = Arrays.copyOf(data, data.length);

6. Wie man eine normale Klasse unveränderlich macht

Wenn Sie eine normale Klasse immutable (unveränderlich) machen möchten, müssen Sie es manuell umsetzen:

  • Alle Felder als private final markieren,
  • Keine Setter hinzufügen,
  • Alle Felder nur im Konstruktor initialisieren,
  • Wenn ein Feld ein veränderliches Objekt ist, eine defensive Kopie erstellen (defensive copy).

Beispiel

public final class User {
    private final String name;
    private final int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String name() { 
        return name; 
    }
    public int age() { 
        return age; 
    }
}

Zugegeben, mit einer Record-Klasse geht das einfacher und kürzer:

public record User(String name, int age) {}

7. Typische Fehler im Umgang mit unveränderlichen Record-Klassen

Fehler Nr. 1: Versuch, ein Feld nach der Erstellung zu ändern.
Anfänger versuchen oft, für ein Record-Objekt p.x = 42; oder p.x(42); zu schreiben. Aber der Compiler sagt sofort: „So geht das nicht! Feld final, kein Setter vorhanden.“

Fehler Nr. 2: Verwendung veränderlicher Objekte als Record-Komponenten.
Wenn Sie einem record ein Feld vom Typ List, Map, ein Array oder ein anderes veränderliches Objekt hinzufügen, schützt der Record Sie nicht vor Änderungen am Inhalt dieses Objekts. Wenn Sie zum Beispiel einen record User(List<String> hobbies) haben, kann jemand ein Element zur Liste hinzufügen oder daraus entfernen – und damit den Zustand Ihres Record-Objekts ändern. Um das zu vermeiden, verwenden Sie unveränderliche Collections (List.copyOf, Collections.unmodifiableList) oder erstellen Sie Kopien der Collections im Konstruktor des Records.

Fehler Nr. 3: Falsches Verständnis von Unveränderlichkeit.
Manche glauben, dass ein record vor jeglichen Änderungen geschützt ist. In Wirklichkeit kann der Inhalt veränderlicher Objekte, auf die die Felder verweisen, geändert werden – was zu unerwarteten Fehlern führt.

1
Aufgabe
JAVA 25 SELF, Level 22, Lektion 1
Gesperrt
Ein Ticket, das sich nicht überschreiben lässt 🎫
Ein Ticket, das sich nicht überschreiben lässt 🎫
1
Aufgabe
JAVA 25 SELF, Level 22, Lektion 1
Gesperrt
Daten historischer Persönlichkeiten 🗿
Daten historischer Persönlichkeiten 🗿
1
Aufgabe
JAVA 25 SELF, Level 22, Lektion 1
Gesperrt
Bewegung eines Objekts auf der Spielkarte 🎮
Bewegung eines Objekts auf der Spielkarte 🎮
1
Aufgabe
JAVA 25 SELF, Level 22, Lektion 1
Gesperrt
Verfolgung von Sensordaten 📊
Verfolgung von Sensordaten 📊
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION