CodeGym /Kurse /JAVA 25 SELF /Einführung in die Serialisierung von Objekten: wozu sie g...

Einführung in die Serialisierung von Objekten: wozu sie gebraucht wird

JAVA 25 SELF
Level 42 , Lektion 0
Verfügbar

1. Wozu Serialisierung dient

Stell dir vor, dein Objekt sind die Dinge, die du in den Urlaub mitnimmst. Serialisierung ist das Verpacken des gesamten Kofferinhalts in einen speziellen Behälter, den man ins Gepäck legen oder per Post verschicken kann. Deserialisierung ist entsprechend das Auspacken dieses Behälters und das Wiedererhalten der Dinge im ursprünglichen Zustand.

Im Kern verwandelt Serialisierung ein Objekt in einen Bytestrom, den man in eine Datei schreiben, über das Netzwerk senden oder einfach im Speicher halten kann. Deserialisierung macht das Gegenteil: Sie stellt das Objekt aus diesem Strom wieder her. Stark vereinfacht ist Serialisierung wie ein „Einfrieren“ des Objekts, um es später wieder „aufzutauen“ und im gleichen Zustand zurückzubekommen.

Zustand von Objekten zwischen Programmstarts speichern

Einer der häufigsten Anwendungsfälle ist das Speichern des Programmzustands. Angenommen, du hast eine Liste von Benutzerinnen und Benutzern, Spielergebnisse oder App‑Einstellungen. All das lässt sich bequem direkt in Form von Objekten speichern. Damit die Daten zwischen Starts nicht verloren gehen, werden sie in eine Datei serialisiert und beim nächsten Start des Programms wieder deserialisiert.

Ein gutes Beispiel ist ein gewöhnlicher Speicherstand in einem Spiel. Wenn der oder die Spielende ein Level geschafft hat, wird der Fortschritt „eingefroren“ und mithilfe der Serialisierung in eine Datei geschrieben. Am nächsten Tag startet er oder sie das Spiel, und der Fortschritt wird „aufgetaut“: Die Daten aus der Datei werden zurück in Objekte verwandelt, und die Person macht genau dort weiter, wo sie aufgehört hat.

Lass uns einen solchen einfachen Speicherstand anlegen:

import java.io.*;

// Die Spielerklasse muss Serializable sein
class Player implements Serializable {
    String name;
    int score;

    Player(String name, int score) {
        this.name = name;
        this.score = score;
    }
}

public class GameSaveExample {
    public static void main(String[] args) throws Exception {
        // Wir erzeugen ein Spielerobjekt
        Player player = new Player("Ihor", 1500);

        // --- Speichern (Serialisierung) ---
        try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("save.dat"))) {
            out.writeObject(player);
            System.out.println("Fortschritt gespeichert!");
        }

        // --- Laden (Deserialisierung) ---
        try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("save.dat"))) {
            Player loaded = (Player) in.readObject();
            System.out.println("Fortschritt geladen: " + loaded.name + " mit Punkten " + loaded.score);
        }
    }
}

Beachte: Damit dieser Code funktioniert, muss die Klasse Player das Interface Serializable implementieren. Mehr dazu in der nächsten Vorlesung!

  • Player ist eine normale Klasse mit den Feldern Name und Punkte, die das Interface Serializable implementiert (implements Serializable).
  • ObjectOutputStream schreibt das Objekt in die Datei "save.dat".
  • ObjectInputStream liest dasselbe Objekt wieder ein.
  • Ergebnis ist ein echter Speicherstand: Beim nächsten Start lädt das Programm das Spielerobjekt mit demselben Zustand.

Objekte über das Netzwerk und zwischen JVMs übertragen

In verteilten Systemen müssen Objekte oft zwischen verschiedenen Programmen oder sogar verschiedenen Maschinen übertragen werden. Beispielsweise hast du einen Client und einen Server, die Nachrichten austauschen sollen. Serialisierung ermöglicht es, ein Objekt auf der einen Seite zu „verpacken“, über das Netzwerk zu senden und auf der anderen Seite wieder „auszupacken“.

Beispiel: Der Client sendet dem Server ein Bestellobjekt (Order), der Server empfängt es, deserialisiert und verarbeitet es.

Einsatz in Java‑Technologien

  • RMI (Remote Method Invocation): ermöglicht den Aufruf entfernter Objekte – Serialisierung wird für die Übergabe von Argumenten und Rückgabewerten benötigt.
  • HTTP‑Sitzungen: In Servlets werden Objekte in einer Session beim Neustart des Containers serialisiert.
  • JMS (Java Message Service): Nachrichten zwischen Komponenten können serialisiert sein.
  • Caching: Objekte können zur Ablage im Cache serialisiert werden (auf die Festplatte oder in einen verteilten Speicher).

Caching und Portabilität

Wenn du Zwischenergebnisse schnell speichern möchtest (zum Beispiel fürs Caching), ist Serialisierung ein hervorragendes Werkzeug. Du serialisierst ein Objekt, speicherst es auf der Festplatte oder im Speicher und stellst es später ohne erneute Berechnungen schnell wieder her.

2. Anwendungsbeispiele für Serialisierung

Eine Benutzersammlung in eine Datei speichern

Angenommen, du hast eine Klasse User:

public class User {
    String name;
    int age;
    // ... weitere Felder
}

Und du hast eine Liste von Benutzern:

List<User> users = new ArrayList<>();
users.add(new User("John", 25));
users.add(new User("Mary", 30));
// ... und so weiter

Um diese Liste in eine Datei zu speichern, serialisierst du sie. Wenn es später benötigt wird, deserialisierst du sie und erhältst genau dieselbe Liste mit denselben Benutzern. Zur Erinnerung: Die Klasse User (und alle ihre Felder) muss die Serialisierung unterstützen, d. h. Serializable implementieren.

Eine Nachricht zwischen Client und Server übertragen

Ein klassisches Beispiel ist ein Chat. Der Benutzer schreibt eine Nachricht, das Objekt Message wird serialisiert und über das Netzwerk gesendet. Der Server erhält den Bytestrom, deserialisiert das Objekt, verarbeitet es und leitet es möglicherweise weiter.

import java.io.*;
import java.net.*;

// Die Nachricht muss Serializable sein
class Message implements Serializable {
    String text;

    Message(String text) {
        this.text = text;
    }
}

// Server
class Server {
    public static void main(String[] args) throws Exception {
        try (ServerSocket serverSocket = new ServerSocket(5000)) {
            System.out.println("Server wartet auf eine Verbindung...");
            Socket socket = serverSocket.accept();
            System.out.println("Client verbunden!");

            try (ObjectInputStream in = new ObjectInputStream(socket.getInputStream())) {
                Message msg = (Message) in.readObject();
                System.out.println("Nachricht empfangen: " + msg.text);
            }
        }
    }
}

// Client
class Client {
    public static void main(String[] args) throws Exception {
        try (Socket socket = new Socket("localhost", 5000)) {
            try (ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream())) {
                Message msg = new Message("Hallo, Server!");
                out.writeObject(msg);
                System.out.println("Nachricht gesendet!");
            }
        }
    }
}

Wie es funktioniert:

  1. Zuerst startet der Server (er wartet auf eine Verbindung).
  2. Dann startet der Client (er verbindet sich mit "localhost:5000").
  3. Der Client serialisiert das Objekt Message und sendet es über den Socket.
  4. Der Server erhält den Bytestrom, deserialisiert ihn und gibt den Text aus.

Hier verwenden wir Sockets (ServerSocket, Socket) – das ist ein Mechanismus für Netzwerkkommunikation, den du später noch genauer lernst. Wichtig sind im Moment nicht die Netzwerkdetails, sondern die Idee: Der Client erzeugt ein Message-Objekt, serialisiert es und sendet es; der Server erhält den Bytestrom, deserialisiert ihn zurück in ein Objekt und gibt die Nachricht aus. Selbst wenn noch unklar ist, was die Klassen ServerSocket und Socket genau tun, zeigt das Beispiel den Wert der Serialisierung: Dank ihr kann man ein Objekt „verpacken“, über das Netzwerk übertragen und auf der anderen Seite ohne Zusatzkonvertierungen wieder auspacken.

Objekte cachen

In großen Anwendungen wird oft Caching eingesetzt, um die Geschwindigkeit zu erhöhen. Zum Beispiel werden Ergebnisse aufwendiger Berechnungen serialisiert und im Cache gespeichert (Datei, Datenbank, verteiltes Speicherbackend). Beim nächsten Abruf lässt sich das Ergebnis schnell wiederherstellen, indem man das Objekt deserialisiert.

import java.io.*;

// Rechenergebnis, das wir cachen möchten
class Result implements Serializable {
    int value;

    Result(int value) {
        this.value = value;
    }
}

public class CacheExample {
    private static final String CACHE_FILE = "cache.dat";

    public static void main(String[] args) throws Exception {
        Result result;

        // Prüfen, ob ein Cache vorhanden ist
        File file = new File(CACHE_FILE);
        if (file.exists()) {
            // Ergebnis aus dem Cache laden
            try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(file))) {
                result = (Result) in.readObject();
                System.out.println("Aus dem Cache geladen: " + result.value);
            }
        } else {
            // „Schwere“ Berechnung (hier nur das Quadrat einer Zahl)
            int x = 12345;
            System.out.println("Wir rechnen... (das dauert)");
            result = new Result(x * x);

            // Ergebnis im Cache speichern
            try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file))) {
                out.writeObject(result);
                System.out.println("Im Cache gespeichert: " + result.value);
            }
        }
    }
}

3. Einschränkungen und Risiken der Serialisierung

Serialisierung ist ein mächtiges Werkzeug, aber nicht ohne Tücken. Schauen wir uns die wichtigsten Einschränkungen und Risiken an.

Nicht alle Objekte lassen sich serialisieren

In Java können nicht alle Objekte „out of the box“ serialisiert werden. Objekte, die mit externen Ressourcen verbunden sind (Dateien, Netzwerkverbindungen, Ein-/Ausgabeströme), sind zum Beispiel nicht serialisierbar. Das ist logisch: Einen „offenen Dateihandle“ oder eine „lebende“ Netzwerkverbindung zu serialisieren ist unmöglich – ihr Zustand hängt vom Betriebssystem und der Laufzeitumgebung ab.

Beispiel: Eine Klasse mit einem Feld vom Typ FileInputStream kann nicht serialisiert werden – beim Versuch zu serialisieren tritt ein Fehler auf.

Sicherheitsaspekte

Serialisierung kann ein Sicherheitsrisiko darstellen. Wenn du Daten aus einer nicht vertrauenswürdigen Quelle (zum Beispiel aus dem Internet) deserialisierst, kann eine böswillige Person einen schädlichen Bytestrom unterjubeln, der zu unerwartetem Verhalten deiner Anwendung führt – im Extremfall sogar zur Ausführung von Schadcode.

Regel: Niemals Daten aus nicht vertrauenswürdigen Quellen deserialisieren! Das ist, als würdest du ein Paket von einer unbekannten Absenderin oder einem unbekannten Absender annehmen – der Inhalt kann alles Mögliche sein.

Versionskompatibilität

Wenn du die Struktur einer Klasse änderst (z. B. ein Feld hinzufügst oder entfernst), können zuvor serialisierte Objekte mit der neuen Version der Klasse inkompatibel werden. Das kann bei der Deserialisierung zu Fehlern führen. Näheres dazu folgt in späteren Vorlesungen.

Performance

Die binäre Serialisierung in Java ist recht schnell, aber nicht immer am kompaktesten und nicht immer praktisch für den Austausch mit anderen Programmiersprachen. Für die Integration mit externen Systemen werden oft Textformate wie JSON oder XML verwendet.

4. Typische Fehler beim ersten Umgang mit Serialisierung

Fehler Nr. 1: Versuch, ein Objekt zu serialisieren, das das Interface Serializable nicht implementiert.
Das Ergebnis ist eine NotSerializableException. Denke daran, im Klassenkopf ausdrücklich implements Serializable anzugeben und darauf zu achten, dass auch alle Felder serialisierbar sind!

Fehler Nr. 2: Serialisierung von Objekten mit nicht serialisierbaren Feldern.
Wenn deine Klasse ein Feld eines Typs enthält, der die Serialisierung nicht unterstützt (z. B. einen Stream oder eine DB‑Verbindung), wird die Serialisierung fehlschlagen. Lösung – solche Felder als transient markieren (mehr dazu später).

Fehler Nr. 3: Deserialisierung von Daten aus unsicheren Quellen.
Das kann zu Sicherheitslücken führen oder sogar zur Ausführung von Schadcode. Vertraue nur Daten, die von deiner eigenen Anwendung serialisiert wurden!

Fehler Nr. 4: Änderungen an der Klassenstruktur nach der Serialisierung.
Wenn du ein Objekt gespeichert hast und anschließend ein Feld in der Klasse hinzufügen oder entfernen würdest, kann es beim Deserialisieren zu Fehlern oder „merkwürdigen“ Werten kommen. Mehr dazu in den nächsten Vorlesungen.

1
Aufgabe
JAVA 25 SELF, Level 42, Lektion 0
Gesperrt
Zeichenkette in ZIP-Archiv komprimieren
Zeichenkette in ZIP-Archiv komprimieren
1
Aufgabe
JAVA 25 SELF, Level 42, Lektion 0
Gesperrt
Datei aus ZIP-Archiv extrahieren
Datei aus ZIP-Archiv extrahieren
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION