1. Einführung: Serialisierung von Objekten mit Referenzfeldern
In realen Anwendungen trifft man selten vollständig „flache“ Klassen. In der Regel enthält ein Objekt andere Objekte, und diese können wiederum noch etwas enthalten. Das nennt man Komposition (oder Verschachtelung) von Objekten. Zum Beispiel:
public class Address {
String city;
String street;
}
public class Person {
String name;
int age;
Address address; // Verschachteltes Objekt!
}
Wenn wir ein solches Objekt serialisieren, stellt sich die Frage: Was passiert mit dem Feld address? Soll Java es zusammen mit Person serialisieren? Und was, wenn Address seinerseits ein weiteres Objekt enthält? Zum Glück (oder leider – je nach Situation) serialisiert Java standardmäßig alle verschachtelten Objekte rekursiv, sofern sie ebenfalls das Interface Serializable implementieren.
Serialisierung in Java ist stets eine tiefe Serialisierung (deep serialization). Das bedeutet, es wird nicht nur das Objekt selbst serialisiert, sondern auch alle Objekte, auf die es über seine (nicht transient) Felder verweist – und so weiter, bis ganz nach unten.
Visualisierung des Prozesses
graph TD
A[Person] --> B[Address]
B --> C[CityInfo]
A --> D[Pet]
Kurz gesagt: Wenn Sie Person serialisieren, serialisiert Java auch Address und alles, was sich innerhalb von Address befindet, und so weiter.
2. Anforderungen an verschachtelte Objekte: Serializable ist Pflicht!
Sie haben vermutlich schon den wichtigen Punkt bemerkt: Alle verschachtelten Objekte, die serialisiert werden, müssen ebenfalls das Interface Serializable implementieren.
Wenn auch nur ein Referenzfeld auf ein Objekt zeigt, das Serializable nicht implementiert, endet der Serialisierungsversuch mit der Ausnahme java.io.NotSerializableException.
Schauen wir uns Beispiele für tiefe Serialisierung an.
Beispiel: alles in Ordnung
import java.io.Serializable;
public class Address implements Serializable {
String city;
String street;
}
public class Person implements Serializable {
String name;
int age;
Address address;
}
Beide Klassen implementieren Serializable. Alles funktioniert, die Serialisierung verläuft erfolgreich.
Beispiel: Fehler!
public class Address { // implementiert Serializable NICHT!
String city;
String street;
}
public class Person implements Serializable {
String name;
int age;
Address address;
}
Wie man sieht, implementiert Address hier das Interface Serializable nicht. Bei der Serialisierung von Person erhalten wir daher eine NotSerializableException.
3. Beispiel: Serialisierung und Deserialisierung mit einem verschachtelten Objekt
Schauen wir uns an, wie das in der Praxis aussieht. In der vorherigen Stufe haben wir mit der Anwendung „Kontaktmanager“ gearbeitet. Fügen wir nun die Adresse jedes Benutzers hinzu.
import java.io.*;
class Address implements Serializable {
String city;
String street;
Address(String city, String street) {
this.city = city;
this.street = street;
}
}
class Person implements Serializable {
String name;
int age;
Address address;
Person(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = address;
}
}
public class SerializationDemo {
public static void main(String[] args) throws Exception {
Person p = new Person("John", 30, new Address("Prag", "Slavinska, 1"));
// Serialisierung
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("person.ser"));
out.writeObject(p);
out.close();
// Deserialisierung
ObjectInputStream in = new ObjectInputStream(new FileInputStream("person.ser"));
Person restored = (Person) in.readObject();
in.close();
System.out.println(restored.name + ", " + restored.age + ", " +
restored.address.city + ", " + restored.address.street);
}
}
Ergebnis:
John, 30, Prag, Slavinska, 1
Alles funktioniert: Das verschachtelte Objekt Address wurde zusammen mit Person serialisiert und wiederhergestellt.
4. Was, wenn das verschachtelte Objekt nicht serialisierbar ist?
Wenn Sie versuchen, ein Objekt zu serialisieren, bei dem mindestens ein Referenzfeld auf ein Objekt zeigt, das Serializable nicht implementiert, wirft Java bereits beim Versuch der Serialisierung eine Ausnahme.
Fehlerdemonstration
class Address { // ist nicht Serializable!
String city;
String street;
}
class Person implements Serializable {
String name;
Address address;
}
public class Test {
public static void main(String[] args) throws Exception {
Person p = new Person();
p.name = "Peter";
p.address = new Address();
p.address.city = "Derry";
p.address.street = "Elm";
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("person.ser"));
out.writeObject(p); // <-- ES WIRD EINE AUSNAHME AUSGEWORFEN!
out.close();
}
}
Fehler:
java.io.NotSerializableException: Address
5. Transient für verschachtelte Objekte
Was tun, wenn Sie ein Referenzfeld auf ein Objekt haben, das nicht serialisiert werden soll (z. B. Cache, Datenbankverbindung, temporäres Objekt)? Deklarieren Sie das Feld in diesem Fall als transient. Dann überspringt Java dieses Feld bei der Serialisierung einfach.
Beispiel mit transient
class Address { // nicht Serializable
String city;
String street;
}
class Person implements Serializable {
String name;
transient Address address; // transient!
Person(String name, Address address) {
this.name = name;
this.address = address;
}
}
public class Test {
public static void main(String[] args) throws Exception {
Address addr = new Address();
addr.city = "Los Santos";
addr.street = "Mulholland Drive";
Person p = new Person("Alex", addr);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("person.ser"));
out.writeObject(p);
out.close();
// Deserialisierung
ObjectInputStream in = new ObjectInputStream(new FileInputStream("person.ser"));
Person restored = (Person) in.readObject();
in.close();
System.out.println(restored.name); // "Alex"
System.out.println(restored.address); // null!
}
}
Ergebnis:
Das Feld address ist nach der Deserialisierung gleich null, weil es transient ist.
6. Tiefe Verschachtelung und Rekursion
Die Serialisierung arbeitet rekursiv: Wenn Person ein Feld Address hat und Address wiederum ein Feld CityInfo usw., taucht der Serialisierer immer tiefer, bis er auf etwas Nicht-Serialisierbares trifft oder der Speicher ausgeht (Scherz – aber nur halb).
Wichtig: zyklische Referenzen
Der Java-Serialisierer kann mit zyklischen Referenzen umgehen. Hat ein Objekt z. B. eine Referenz auf ein anderes Objekt, das wiederum zurückverweist, gerät der Serialisierer nicht in eine Endlosschleife, sondern speichert die Struktur korrekt.
class A implements Serializable {
B b;
}
class B implements Serializable {
A a;
}
Wenn Sie die Objekte A und B erstellen, die gegenseitig aufeinander verweisen, löst die Serialisierung keinen StackOverflowError aus – Java merkt sich bereits serialisierte Objekte.
7. Beispiel: Serialisierung eines Objekts mit verschachtelter Liste
Häufig enthält ein Objekt Collections anderer Objekte. Zum Beispiel kann ein Benutzer eine Freundeliste haben:
import java.io.*;
import java.util.*;
class Person implements Serializable {
String name;
List<Person> friends;
Person(String name) {
this.name = name;
this.friends = new ArrayList<>();
}
}
public class FriendsSerialization {
public static void main(String[] args) throws Exception {
Person alice = new Person("Alice");
Person bob = new Person("Bob");
alice.friends.add(bob);
// Serialisierung
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("friends.ser"));
out.writeObject(alice);
out.close();
// Deserialisierung
ObjectInputStream in = new ObjectInputStream(new FileInputStream("friends.ser"));
Person restored = (Person) in.readObject();
in.close();
System.out.println(restored.name); // Alice
System.out.println(restored.friends.get(0).name); // Bob
}
}
Wichtig: Die Collections der Java-Standardbibliothek (ArrayList, HashMap u. a.) implementieren Serializable, daher funktioniert alles „out of the box“.
8. Typische Fehler bei der Serialisierung verschachtelter Objekte
Fehler Nr. 1: Eines der verschachtelten Objekte implementiert Serializable nicht. Sie haben in einer der verschachtelten Klassen implements Serializable vergessen. Ergebnis – NotSerializableException beim ersten Serialisierungsversuch. Prüfen Sie, dass alle Klassen in Ihrer Serialisierungskette dieses Interface unterstützen.
Fehler Nr. 2: Ein nicht serialisierbares Feld ist nicht als transient deklariert. Wenn Sie ein Feld haben, das nicht serialisiert werden kann (z. B. Stream, DB-Verbindung, etwas Temporäres), es aber nicht als transient markieren, schlägt die Serialisierung mit einem Fehler fehl. Vergessen Sie transient nicht!
Fehler Nr. 3: Nicht übereinstimmende serialVersionUID in verschachtelten Klassen. Wenn Sie serialVersionUID in verschachtelten Klassen explizit deklarieren und deren Struktur ändern, denken Sie daran, diese Kennung zu aktualisieren – sonst sind Fehler bei der Deserialisierung möglich.
Fehler Nr. 4: Mutierende (veränderliche) verschachtelte Objekte. Wenn Sie eine Collection oder ein Objekt serialisieren, das sich später ändert (z. B. eine Freundeliste), ist das nach der Deserialisierung ein „Schnappschuss“ zum Zeitpunkt der Serialisierung. Neue Änderungen am Original spiegeln sich im deserialisierten Objekt nicht wider.
Fehler Nr. 5: Serialisierung riesiger Objektgraphen. Wenn Sie eine sehr komplexe Struktur mit vielen verschachtelten Objekten haben, kann die Serialisierung viel Zeit und Speicher benötigen. Manchmal ist es besser, nur die Kerndaten zu serialisieren und nicht die gesamte Struktur.
GO TO FULL VERSION