1. Einleitung
In Java wird zur Serialisierung von Objekten meist das Interface Serializable verwendet. Es ist einfach: Implementieren Sie das Interface, und das Objekt lässt sich mit ObjectOutputStream/ObjectInputStream schreiben/lesen. Manchmal reicht das jedoch nicht aus:
- Sie müssen vollständig kontrollieren, welche Felder und wie diese serialisiert werden.
- Es muss Kompatibilität zwischen verschiedenen Versionen einer Klasse gewährleistet sein.
- Es ist wichtig, die Größe der serialisierten Datei zu verringern oder den Prozess zu beschleunigen.
Für solche Fälle gibt es in Java das Interface Externalizable – ein „manueller“ und flexiblerer Ansatz der Serialisierung.
Kurzfassung:
- Serializable – automatische Serialisierung: Java entscheidet selbst, was und wie geschrieben wird.
- Externalizable – manuelle Serialisierung: Sie geben selbst an, was und wie gespeichert/wiederhergestellt wird.
2. Vertrag Externalizable: writeExternal und readExternal implementieren
Um Externalizable zu verwenden, müssen Sie:
- Das Interface java.io.Externalizable implementieren.
- Unbedingt zwei Methoden implementieren:
- void writeExternal(ObjectOutput out) throws IOException
- void readExternal(ObjectInput in) throws IOException, ClassNotFoundException
Beispiel:
import java.io.*;
public class User implements Externalizable {
private String name;
private int age;
// Pflicht: öffentlicher parameterloser Konstruktor!
public User() {}
public User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeUTF(name);
out.writeInt(age);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
name = in.readUTF();
age = in.readInt();
}
@Override
public String toString() {
return name + " (" + age + ")";
}
}
Wichtig: Der Entwickler entscheidet selbst, welche Felder serialisiert werden und in welcher Reihenfolge. Es gibt jedoch eine zwingende Anforderung: Die Klasse muss einen public-Konstruktor ohne Parameter besitzen. Andernfalls wirft die Deserialisierung eine InvalidClassException.
3. Wann sollte Externalizable verwendet werden?
Verwenden Sie Externalizable, wenn:
- Sie vollständige Kontrolle über das Datenformat benötigen. Zum Beispiel möchten Sie nur einen Teil der Felder serialisieren oder diese in einer besonderen Reihenfolge/in einem besonderen Format serialisieren.
- Optimierung von Performance und Dateigröße. Die Standardserialisierung fügt Verwaltungsinformationen hinzu (Metadaten, Klassennamen, Typen usw.). Mit Externalizable schreiben Sie nur die benötigten Daten.
- Sicherstellung der Abwärtskompatibilität. Wenn sich die Struktur der Klasse ändert, können Sie die Logik zum Lesen alter und neuer Datenversionen manuell implementieren.
- Serialisierung nicht standardmäßiger Objekte. Zum Beispiel, wenn es Felder gibt, die sich mit dem Standardverfahren nicht serialisieren lassen (z. B. transient, volatile oder komplexe Strukturen).
Wann sollten Sie es NICHT verwenden?
- Wenn Sie keine vollständige Kontrolle benötigen – verwenden Sie Serializable; das ist einfacher und sicherer.
- Wenn Sie nicht sicher sind, dass Sie die Kompatibilität des Datenformats bei Klassenänderungen gewährleisten können.
4. Vor- und Nachteile von Externalizable gegenüber Serializable
Vorteile:
- Volle Kontrolle über die Serialisierung. Sie entscheiden, was und wie geschrieben/gelesen wird.
- Kompaktheit. Keine überflüssigen Metadaten – nur Ihre Daten.
- Geschwindigkeit. Weniger Daten – schnelleres Schreiben/Lesen.
- Flexibilität. Sie können Unterstützung für verschiedene Formatversionen implementieren, Komprimierung, Verschlüsselung usw. hinzufügen.
Nachteile:
- Manuelle Implementierung – Fehler sind leicht möglich. Wenn die Reihenfolge von Schreiben/Lesen vertauscht wird, „bricht“ die Serialisierung (Fehler oder inkorrekte Daten).
- Keine automatische Unterstützung für transient, serialVersionUID. Alles muss durchdacht und manuell umgesetzt werden.
- Wartungsaufwendiger. Bei Änderungen an der Klassenstruktur müssen die Serialisierungsmethoden aktualisiert werden.
- Öffentlicher parameterloser Konstruktor ist obligatorisch.
- Weniger „Magie“ – mehr Verantwortung.
5. Beispiele: Serialisierung und Deserialisierung eines einfachen Objekts
Serialisierung eines Objekts
User user = new User("Alice", 30);
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("user.bin"))) {
out.writeObject(user);
}
Deserialisierung eines Objekts
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("user.bin"))) {
User loaded = (User) in.readObject();
System.out.println(loaded); // Alice (30)
}
Achtung: Wenn Sie die Reihenfolge des Schreibens/Lesens der Felder ändern oder vergessen, ein Feld zu serialisieren, sind die Daten inkorrekt! Die Methoden writeExternal und readExternal müssen strikt in der Reihenfolge der Operationen aufeinander abgestimmt sein.
Beispiel: Nur einen Teil der Felder serialisieren
public class SecretUser implements Externalizable {
private String login;
private transient String password; // transient ist für Externalizable ohne Bedeutung
public SecretUser() {}
public SecretUser(String login, String password) {
this.login = login;
this.password = password;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeUTF(login);
// Das Passwort wird nicht serialisiert!
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
login = in.readUTF();
password = null; // Passwort wird nicht wiederhergestellt
}
}
6. Praxis: Vergleich der Größe der serialisierten Datei
Vergleichen wir, wie „schwer“ die Dateien sind, die über Serializable und über Externalizable serialisiert wurden.
Klasse mit Serializable
public class UserSerializable implements Serializable {
private String name;
private int age;
public UserSerializable(String name, int age) {
this.name = name;
this.age = age;
}
}
Klasse mit Externalizable
public class UserExternalizable implements Externalizable {
private String name;
private int age;
public UserExternalizable() {}
public UserExternalizable(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeUTF(name);
out.writeInt(age);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
name = in.readUTF();
age = in.readInt();
}
}
Vergleichscode
import java.io.*;
public class CompareSerialization {
public static void main(String[] args) throws Exception {
UserSerializable s = new UserSerializable("Bob", 25);
UserExternalizable e = new UserExternalizable("Bob", 25);
// Serializable
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.bin"))) {
out.writeObject(s);
}
// Externalizable
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ext.bin"))) {
out.writeObject(e);
}
System.out.println("Serializable file size: " + new File("ser.bin").length());
System.out.println("Externalizable file size: " + new File("ext.bin").length());
}
}
Ergebnis:
Die Datei ser.bin (Serializable) ist in der Regel größer – sie enthält Verwaltungsinformationen von Java. Die Datei ext.bin (Externalizable) enthält nur Ihre Daten und ist üblicherweise kleiner.
7. Typische Fehler bei der Arbeit mit Externalizable
Fehler Nr. 1: Fehlender öffentlicher Konstruktor ohne Parameter.
Eine Klasse, die Externalizable implementiert, muss unbedingt einen public-Konstruktor ohne Argumente haben. Ohne diesen führt die Deserialisierung zu einer InvalidClassException.
Fehler Nr. 2: Verletzung der Schreib-/Lesereihenfolge der Felder.
Die Methoden writeExternal und readExternal müssen in derselben Reihenfolge arbeiten. Wenn beim Schreiben zuerst das Feld name gespeichert wird, beim Lesen jedoch zuerst versucht wird, age zu lesen, werden die Daten verfälscht.
Fehler Nr. 3: Ausgelassene Felder bei der Serialisierung.
Wenn ein Feld in writeExternal nicht geschrieben wird, hat es nach der Deserialisierung den Wert null (bei Referenztypen) bzw. 0 (bei numerischen Typen).
Fehler Nr. 4: Falsche Verwendung von transient oder serialVersionUID.
Im Gegensatz zu Serializable funktionieren diese Mechanismen bei Externalizable nicht automatisch – Sie müssen selbst steuern, welche Felder gespeichert und welche ausgelassen werden.
Fehler Nr. 5: Änderung der Klassenstruktur ohne Aktualisierung der Methoden.
Wenn Felder hinzugefügt oder entfernt werden und die entsprechenden Änderungen nicht in writeExternal und readExternal einfließen, können zuvor gespeicherte Daten nicht mehr korrekt geladen werden.
GO TO FULL VERSION