CodeGym /Kurse /JAVA 25 SELF /Analyse typischer Fehler bei der Serialisierung von Colle...

Analyse typischer Fehler bei der Serialisierung von Collections

JAVA 25 SELF
Level 44 , Lektion 4
Verfügbar

1. NotSerializableException: wenn sich eine Collection nicht serialisieren lässt

Der häufigste und zugleich tückischste Fehler bei der Serialisierung von Collections ist die java.io.NotSerializableException. Sie tritt auf, wenn mindestens ein Element der Collection das Interface Serializable nicht implementiert.

Sehen wir uns ein naives Beispiel an:

import java.io.*;
import java.util.*;

class Book {
    String title;
    Book(String title) { this.title = title; }
}

public class LibraryApp {
    public static void main(String[] args) throws Exception {
        List<Book> books = new ArrayList<>();
        books.add(new Book("Dombey und Sohn"));

        // Versuch, die Sammlung zu serialisieren
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("books.ser"))) {
            oos.writeObject(books); // Boom! NotSerializableException
        }
    }
}

Was passiert? Beim Schritt oos.writeObject(books) erhalten Sie die Ausnahme:

java.io.NotSerializableException: Book

Warum? Weil die Klasse Book das Interface Serializable nicht implementiert. Selbst wenn die Collection (ArrayList) serialisierbar ist, müssen die Elemente der Collection ebenfalls serialisierbar sein!

So diagnostizieren Sie

In der Fehlermeldung ist stets die Klasse genannt, die das Problem verursacht hat – suchen Sie sie im Exception-Text. Ist die Collection groß und tritt der Fehler nur unter bestimmten Bedingungen auf, wurde möglicherweise ein Element versehentlich hinzugefügt, das Serializable nicht implementiert.

So beheben Sie den Fehler

Fügen Sie Ihrer Klasse implements Serializable hinzu:

class Book implements Serializable {
    String title;
    Book(String title) { this.title = title; }
}

Tipp: Wenn die Collection unterschiedliche Objekttypen enthält, prüfen Sie sie alle auf Serializable!

2. ClassCastException bei der Deserialisierung: wenn Generics tückisch werden

In Java wird die Information über Generic-Parameter von Collections nach der Kompilierung getilgt (Type Erasure). Das bedeutet: Wenn Sie eine List<String> serialisiert und als List<Integer> deserialisieren, merkt der Compiler den Fehler nicht, zur Laufzeit erhalten Sie jedoch eine ClassCastException.

Beispiel:

// Serialisierung
List<String> names = Arrays.asList("Anna", "Boris");
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("names.ser"))) {
    oos.writeObject(names);
}

// Deserialisierung (GEFÄHRLICH!)
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("names.ser"))) {
    List<Integer> numbers = (List<Integer>) ois.readObject(); // unchecked cast
    Integer first = numbers.get(0); // Boom! ClassCastException
}

Fehler:

java.lang.ClassCastException: class java.lang.String cannot be cast to class java.lang.Integer

So vermeiden Sie das

  • Verwenden Sie keine „rohen“ Collections (raw types) und casten Sie nicht ohne Not.
  • Prüfen Sie die Elementtypen nach der Deserialisierung, wenn Sie sich über den Inhalt nicht sicher sind.
  • Dokumentieren Sie, welcher Collection-Typ serialisiert wird und beim Lesen erwartet wird.

Beispiel für eine sichere Deserialisierung:

Object obj = ois.readObject();
if (obj instanceof List<?>) {
    List<?> list = (List<?>) obj;
    if (!list.isEmpty() && list.get(0) instanceof String) {
        @SuppressWarnings("unchecked")
        List<String> safeNames = (List<String>) obj; // Warnung unterdrückt, aber der Typ wurde geprüft!
    }
}

3. Änderung der Klassenstruktur: serialVersionUID und Abwärtskompatibilität

Sie haben eine Collection serialisiert und dann beschlossen, der Elementklasse ein neues Feld hinzuzufügen, einen Feldnamen zu ändern oder die Klassenstruktur insgesamt zu verändern. Beim Versuch, die alte Datei zu deserialisieren, erhalten Sie nun eine rätselhafte Fehlermeldung:

java.io.InvalidClassException: Book; local class incompatible: stream classdesc serialVersionUID = 1234, local class serialVersionUID = 5678

Warum passiert das?

Jede serialisierbare Klasse erhält eine eindeutige Versionskennung – serialVersionUID. Wenn sich die Klasse ändert (zum Beispiel fügen Sie ein Feld hinzu), berechnet die JVM eine neue serialVersionUID, und bei der Deserialisierung wird festgestellt, dass die Version der Klasse nicht mit der beim Serialisieren übereinstimmt.

So vermeiden Sie das

  • Deklarieren Sie serialVersionUID ausdrücklich in Ihren Klassen:
class Book implements Serializable {
    private static final long serialVersionUID = 1L;
    String title;
    // ...
}
  • Bewahren Sie Abwärtskompatibilität: Entfernen oder benennen Sie Felder nicht um, wenn Sie alte Dateien lesen möchten.
  • Testen Sie die Deserialisierung nach Änderungen.

Was tun, wenn die Klasse dennoch geändert werden muss?

  • Erwägen Sie die Implementierung der Methoden readObject/writeObject für eine manuelle Steuerung der Serialisierung.
  • Oder migrieren Sie die Daten: Lesen Sie die alte Datei mit der alten Klassenversion ein und speichern Sie sie anschließend im neuen Format neu.

4. Datenverlust bei der Serialisierung unveränderlicher Collections

In neueren Java-Versionen gibt es unveränderliche Collections, zum Beispiel erstellt über List.of(), Set.of(), Map.of(). In älteren Java-Versionen (vor 12) und in einigen Drittanbieter-Implementierungen kann die Serialisierung solcher Collections fehlerhaft sein: Nach der Deserialisierung wird die Collection veränderlich oder es tritt ein Fehler auf.

Beispiel:

List<String> list = List.of("a", "b", "c");
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("list.ser"))) {
    oos.writeObject(list);
}

Auf älteren JVMs trat bei der Deserialisierung ein Fehler auf oder die Collection war anschließend nicht mehr unveränderlich.

So vermeiden Sie das

  • Prüfen Sie die Dokumentation für die verwendete Java-Version.
  • Testen Sie Serialisierung und Deserialisierung solcher Collections.
  • Wenn Sie die Unveränderlichkeit bewahren müssen, umhüllen Sie die Collection nach der Deserialisierung mit Collections.unmodifiableList(list).

5. Serialisierung von transient- und static-Feldern

Was passiert mit solchen Feldern:

  • transient – mit diesem Schlüsselwort markierte Felder werden überhaupt nicht serialisiert. Nach der Deserialisierung haben sie den Standardwert (zum Beispiel null oder 0).
  • static – Klassenfelder (und nicht Objektfelder) werden niemals serialisiert.

Beispiel:

class Book implements Serializable {
    String title;
    transient String cache; // wird nicht serialisiert!
    static String publisher = "Default"; // wird ebenfalls nicht serialisiert!
}

Warum ist das wichtig

Wenn Sie berechnete Werte oder einen Cache im Objekt speichern, markieren Sie diese als transient – das spart Platz und beschleunigt die Serialisierung.

Achtung: Nach der Deserialisierung müssen transient-Felder neu berechnet oder erneut initialisiert werden.

6. Serialisierung großer Collections: Leistung und Dateigröße

Probleme:

  • Große Collections (zum Beispiel eine Million Objekte) können zu sehr großen Dateien, langen Schreib-/Lesezeiten und gelegentlich sogar zu Speichermangel führen (OutOfMemoryError).
  • Bei der Serialisierung eines Objektgraphen (zum Beispiel komplexer, miteinander verbundener Collections) kann die Dateigröße unerwartet stark anwachsen.

So vermeiden Sie das

  • Serialisieren Sie die Collection in Teilen: Schreiben Sie Objekte beispielsweise einzeln oder in kleinen Batches.
  • Verwenden Sie Streaming-Verarbeitung: Statt die gesamte Collection auf einmal zu serialisieren, serialisieren Sie Elemente nach Bedarf.
  • Komprimieren Sie Dateien: Nutzen Sie GZIPOutputStream, um die Dateigröße zu reduzieren.

Beispiel für eine Streaming-Serialisierung:

try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("books.ser"))) {
    for (Book book : bigList) {
        oos.writeObject(book);
    }
}

Achtung: Bei diesem Ansatz muss die Deserialisierung wissen, wie viele Objekte geschrieben wurden (oder einen speziellen „Endmarker“ verwenden).

1
Aufgabe
JAVA 25 SELF, Level 44, Lektion 4
Gesperrt
Falsche Interpretation von Daten: `ClassCastException` nach der Deserialisierung 🚨
Falsche Interpretation von Daten: `ClassCastException` nach der Deserialisierung 🚨
1
Aufgabe
JAVA 25 SELF, Level 44, Lektion 4
Gesperrt
Digitaler Archivar: effiziente Speicherung großer Datenmengen mit Kompression 🗄️
Digitaler Archivar: effiziente Speicherung großer Datenmengen mit Kompression 🗄️
1
Umfrage/Quiz
Serialisierung komplexer Strukturen, Level 44, Lektion 4
Nicht verfügbar
Serialisierung komplexer Strukturen
Serialisierung komplexer Strukturen
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION