CodeGym /Kurse /JAVA 25 SELF /transient-Felder, serialVersionUID

transient-Felder, serialVersionUID

JAVA 25 SELF
Level 43 , Lektion 1
Verfügbar

1. Mehr über transient

In Java ist das Schlüsselwort transient – eine Möglichkeit, dem Serialisierer zu sagen: „Bitte fasse dieses Feld nicht an, vergiss es beim Speichern des Objekts!“. Wenn Sie ein Feld als transient deklarieren, wird es nicht in den serialisierten Bytestrom aufgenommen. Das ist besonders nützlich für sensible Daten (z. B. Passwörter) oder temporäre Berechnungen, die nicht gespeichert werden müssen.

Beispiel: Wozu braucht man transient?

Angenommen, wir haben eine Benutzerklasse:

import java.io.Serializable;

public class User implements Serializable {
    private String username;
    private transient String password; // Wir möchten das Passwort nicht speichern!

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    // Hier gäbe es Getter und Setter
}

Wenn wir ein Objekt dieser Klasse serialisieren, wird das Feld password nicht in die Datei (oder einen anderen Stream) geschrieben. Das bedeutet, dass es bei der Deserialisierung den Standardwert hat – für Objekte ist das null, für Zahlen 0, für booleanfalse.

Wie funktioniert das in der Praxis?

Lassen Sie uns ein Mini-Experiment durchführen. Zuerst serialisieren wir den Benutzer:

import java.io.*;

public class TransientDemo {
    public static void main(String[] args) throws Exception {
        User user = new User("rutger", "qwerty123");

        // Objekt in Datei speichern
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("user.ser"));
        out.writeObject(user);
        out.close();

        // Objekt wieder einlesen
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("user.ser"));
        User restored = (User) in.readObject();
        in.close();

        System.out.println("Username: " + restored.username);
        System.out.println("Password: " + restored.password);
    }
}

Ergebnis:

Username: rutger
Password: null

Wie Sie sehen, wurde das Feld password nicht wiederhergestellt – es ist transient, daher hat der Serialisierer es ignoriert.

Wo und wofür verwendet man transient?

  • Passwörter und Tokens. Serialisieren Sie sie niemals!
  • Zwischengespeicherte oder temporäre Daten. Zum Beispiel, wenn Sie ein Feld haben, das sich „on the fly“ berechnen lässt.
  • Objekte, die man nicht serialisieren kann oder nicht sollte. Zum Beispiel Referenzen auf Datenbankverbindungen, Streams, Sockets.

Besonderheiten des Verhaltens von transient-Feldern

Wenn ein Objekt deserialisiert wird, erhalten alle als transient markierten Felder Standardwerte. Wenn Sie ihnen wieder Bedeutung geben müssen, können Sie die Methode readObject verwenden und sie manuell auffüllen (Cache neu berechnen, das Passwort beim Benutzer abfragen usw.).

2. serialVersionUID: eindeutiger Versionsbezeichner der Klasse

serialVersionUID ist ein spezielles statisches Feld vom Typ long, das die „Version“ einer serialisierbaren Klasse definiert. Beim Serialisieren wird der Wert von serialVersionUID geschrieben, bei der Deserialisierung vergleicht die JVM ihn mit dem Wert in der aktuellen Klasse. Wenn sie nicht übereinstimmen – wird eine Ausnahme ausgelöst und das Objekt nicht wiederhergestellt.

Wie deklariert man serialVersionUID?

Ganz einfach:

private static final long serialVersionUID = 1L;

In der Regel deklariert man es direkt in der Klasse, die Serializable implementiert:

import java.io.Serializable;

public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    // ... weitere Felder und Methoden
}

Wozu dient serialVersionUID?

Stellen Sie sich vor, Sie haben ein Objekt einer Klasse in eine Datei gespeichert und danach die Klassenstruktur geändert (ein Feld hinzugefügt, etwas umbenannt usw.). Wenn sich die serialVersionUID unterscheidet, hält die JVM die Klasse für inkompatibel mit der alten Version und lässt das Objekt nicht deserialisieren. Das verhindert unerwartete Fehler.

Was passiert, wenn man serialVersionUID nicht deklariert?

Wenn Sie serialVersionUID nicht explizit deklarieren, wird sie automatisch erzeugt – auf Grundlage der Klassenstruktur. Doch schon eine kleine Änderung (zum Beispiel ein Feld hinzugefügt oder entfernt) führt zu einer anderen serialVersionUID. In der Folge können Sie Objekte, die mit der alten Version gespeichert wurden, nicht mehr deserialisieren.

Daher wird empfohlen, serialVersionUID immer explizit festzulegen!

Demonstration: serialVersionUID stimmt nicht überein

1) Zuerst erstellen wir die Klasse und serialisieren ein Objekt:

import java.io.Serializable;

public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    private String username;

    public User(String username) {
        this.username = username;
    }
}

2) Danach ändern wir die serialVersionUID:

import java.io.Serializable;

public class User implements Serializable {
    private static final long serialVersionUID = 2L; // War 1L, jetzt 2L!
    private String username;

    public User(String username) {
        this.username = username;
    }
}

Ergebnis:

java.io.InvalidClassException: User; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2

Die JVM warnt deutlich: „Versionen sind nicht kompatibel!“

Welchen Wert sollte man für serialVersionUID wählen?

Meistens verwendet man einfache Werte (1L, 2L, 42L), und in großen Projekten generiert die IDE „lange“ Werte. Wichtig ist, ihn nur dann zu ändern, wenn sich die Klassenstruktur inkompatibel ändert.

3. Praxis: transient-Felder und serialVersionUID in Aktion

Beispiel: Klasse mit einem transient-Feld

Lassen Sie uns eine Übungsanwendung (zum Beispiel einen Kontaktmanager) anpassen und der Benutzerklasse ein Feld zum Speichern eines temporären Authentifizierungs-Tokens hinzufügen, das nicht serialisiert werden soll.

import java.io.Serializable;

public class Contact implements Serializable {
    private static final long serialVersionUID = 1L;

    private String name;
    private String phone;
    private transient String sessionToken; // temporäres Token

    public Contact(String name, String phone, String sessionToken) {
        this.name = name;
        this.phone = phone;
        this.sessionToken = sessionToken;
    }

    @Override
    public String toString() {
        return "Contact{" +
               "name='" + name + '\'' +
               ", phone='" + phone + '\'' +
               ", sessionToken='" + sessionToken + '\'' +
               '}';
    }
}

Versuchen wir nun, das Objekt zu serialisieren und wieder zu deserialisieren:

import java.io.*;

public class TransientAndSUIDDemo {
    public static void main(String[] args) throws Exception {
        Contact c = new Contact("John", "+19990001122", "token-12345");

        // Objekt speichern
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("contact.ser"));
        out.writeObject(c);
        out.close();

        // Objekt wiederherstellen
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("contact.ser"));
        Contact restored = (Contact) in.readObject();
        in.close();

        System.out.println("Vor der Serialisierung: " + c);
        System.out.println("Nach der Deserialisierung: " + restored);
    }
}

Ausgabe:

Vor der Serialisierung: Contact{name='John', phone='+19990001122', sessionToken='token-12345'}
Nach der Deserialisierung: Contact{name='John', phone='+19990001122', sessionToken='null'}

Wie Sie sehen, wurde das Feld sessionToken nicht wiederhergestellt – es ist transient.

Beispiel: Experiment mit serialVersionUID

1) Zuerst serialisieren wir ein Objekt mit serialVersionUID = 1L.
2) Dann ändern wir die serialVersionUID auf 2L und versuchen, dieselbe Datei zu deserialisieren.

Ergebnis: Sie erhalten eine InvalidClassException, wie oben gezeigt.

4. Warum sollte man serialVersionUID besser explizit festlegen?

  • Explizit ist besser als implizit. Sie steuern die Kompatibilität: Wenn sich die Klassenstruktur nicht kritisch geändert hat, lassen Sie die alte serialVersionUID stehen, und die Objekte lassen sich problemlos deserialisieren.
  • Automatische Generierung ist riskant. Jede Änderung kann den berechneten Wert verändern und die Kompatibilität der gespeicherten Daten „brechen“.
  • IDE hilft. Die meisten IDEs (z. B. IntelliJ IDEA) können serialVersionUID automatisch generieren.

5. Häufige Fehler im Umgang mit transient und serialVersionUID

Fehler Nr. 1: sensibles Feld nicht als transient markiert.
Dadurch landen Passwörter oder Tokens versehentlich in serialisierten Dateien. Das ist nicht nur peinlich, sondern auch gefährlich.

Fehler Nr. 2: serialVersionUID nicht explizit deklariert.
Die Klasse wurde geändert und nun lassen sich alte Objekte nicht deserialisieren: Die JVM hält sie für inkompatibel, obwohl sich die Struktur inhaltlich nicht kritisch verändert haben könnte.

Fehler Nr. 3: serialVersionUID ohne Notwendigkeit geändert.
Wenn Sie nur einen Getter oder Kommentar hinzugefügt haben, müssen Sie die serialVersionUID nicht ändern – sonst lassen sich alte Daten nicht mehr deserialisieren.

Fehler Nr. 4: serialVersionUID ist nicht static oder nicht final.
Das Feld muss als private static final long serialVersionUID deklariert sein. Andernfalls interpretiert die JVM es nicht korrekt.

Fehler Nr. 5: transient-Feld nach der Deserialisierung nicht wiederhergestellt.
Wenn der Wert für das Objekt kritisch ist, stellen Sie ihn in readObject wieder her – sonst kann das Objekt fehlerhaft arbeiten.

1
Aufgabe
JAVA 25 SELF, Level 43, Lektion 1
Gesperrt
Zeitstempel für personenbezogene Daten: Versionierung einer Entität
Zeitstempel für personenbezogene Daten: Versionierung einer Entität
1
Aufgabe
JAVA 25 SELF, Level 43, Lektion 1
Gesperrt
Gehalt-Geheimnisse: Vorübergehendes Verschwinden und Wiederherstellung auf Standardwert
Gehalt-Geheimnisse: Vorübergehendes Verschwinden und Wiederherstellung auf Standardwert
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION