22.5 Serialisierung und Persistenz

Werbung
22.5
Serialisierung und Persistenz
In den meisten Anwendungsszenarien werden Daten nicht nur während der Laufzeit
eines Programmes verwendet, sondern müssen dauerhaft (persistent) und unabhängig von der virtuellen Maschine verfügbar sein. So macht es beispielsweise wenig
Sinn, in einer Debitoren- und Kreditorenverwaltung jeden Kunden bei Programmstart neu zu erfassen. Für solche Zwecke stehen eigens Datenbanksysteme zur Verfügung, die diese Informationen persistent, konsistent und effizient verwalten. Auf
die Möglichkeit des Datenbankzugriffs aus Java werden wir später noch eingehen.
In diesem Abschnitt befassen wir uns jedoch mit den Klassen ObjectInputStream und ObjectOutputStream, die eine einfache Möglichkeit darstellen, Objektpersistenz unabhängig von Datenbanksystemen zu erreichen.
In den vorangehenden Beispielen haben wir bereits gesehen, wie Zeichen oder
Bytes in Dateien geschrieben und wieder gelesen werden können. Mit den erwähnten ObjectStreams lässt sich dieses Prinzip auf beliebige Objekte verallgemeinern:
Die Klasse ObjectOutputStream kann beliebige Objekte in einen Byte-Strom
zerlegen (Serialisierung), der dann in eine Datei geschrieben wird. Bei der Serialisierung wird die Struktur des Objekts durchlaufen und sowohl strukturelle als auch inhaltliche Information in den Byte-Strom geschrieben (vgl. Abbildung 22.2). Bei der
Deserialisierung (durch die Klasse ObjectInputStream) wird umgekehrt ein
Byte-Strom gelesen und das ursprüngliche Objekt wieder rekonstruiert.
Adresse
String strasse;
String zusatz
String land;
String plz;
String ort;
Kunde
int kundenNr;
String name;
Adresse adr;
...
100017
SAP AG
Hopp-Allee
1-12
Deutschland
69189
Walldorf
...
ObjectOutputStream
Abb. 22.2: Serialisierung eines Objekts
Da ein Objekt selbst wieder Referenzen auf Objekte als Attribute besitzen kann,
handelt es sich bei der Serialisierung und Deserialisierung um Verfahren, die rekursiv arbeiten und zyklische Abhängigkeiten sowie mehrfache Referenzen auf das gleiche Objekt berücksichtigen. Die konkrete Implementierung der Serialisierung und
371
Deserialisierungsverfahren bleiben dem Java-Entwickler verborgen, es sei denn, man
studiert den Quelltext der beteiligten Klassen.
Betrachten wir folgendes Beispiel, bei dem ein Zeitstempel in eine Datei geschrieben, nach kurzer Zeit aus dieser gelesen und an der Konsole ausgegeben wird; aus
Gründen der Übersichtlichkeit verzichten wir hier auf eine detaillierte Ausnahmebehandlung:
import java.io.*;
import java.util.*;
public class WriteAndReadDate {
public static void main( String[] args ) throws Exception {
// OutputStream erzeugen
ObjectOutputStream os = new ObjectOutputStream(
new FileOutputStream("l.ser") );
Date d = new Date();
// aktuellen Zeitstempel ...
os.writeObject( d );
// ... in den Strom schreiben
os.close();
// Strom schließen
Thread.sleep( 2400 );
// eine Weile warten
// InputStream erzeugen
ObjectInputStream is = new ObjectInputStream(
new FileInputStream("l.ser") );
Object o = is.readObject(); // Object aus dem Strom lesen
is.close();
// Strom schließen
System.out.println( o );
// Inhalt ausgeben
// aktuellen Zeitstempel zum Vergleich ausgeben
System.out.println( new Date() );
}
}
Man erzeugt einen ObjectOutputStream, indem dem Konstruktor eine Instanz
von FileOutputStream als Argument übergeben wird. Auch hier tritt das bereits
erwähnte "Decorator-Pattern" auf: ein OutputStream wird mit der zusätzlichen Fähigkeit versehen, auch Objekte zu serialisieren. Analog erhält man eine Instanz von
ObjectInputStream, indem ein FileInputStream dekoriert wird.
Um ein Objekt in den Strom zu schreiben, wird die Methode writeObject() aufgerufen, der man das zu schreibende Objekt übergibt. Zum Lesen verwendet man
die Methode readObject(), die das gelesene Objekt zurück gibt.
Grundsätzlich können mit diesem Verfahren beliebige Objekte unterschiedlicher
Klassen serialisiert werden, sofern diese das Interface Serializable implementie-
ren; dabei handelt es sich um ein so genanntes Marker-Interface, das weder Methoden noch Attribute besitzt und lediglich dazu dient, Serialisierbarkeit zu kennzeichnen. Attribute eines Objekts, die mit dem Schlüsselwort transient (vergänglich,
kurzlebig) gekennzeichnet sind, werden von der Serialisierung ausgenommen und
beim Deserialisieren mit ihrem Default-Wert belegt. Der Versuch, Instanzen von
Klassen zu serialisieren, die nicht Serializable implementieren, scheitert mit einer NotSerializableException. Dies passiert auch, wenn die Klasse ein weder
static noch transient gekennzeichnetes Attribut besitzt, das nicht vom Typ
Serializable ist.
Werden Instanzen unterschiedlicher Klassen mit dem dargestellten Verfahren in einer einzigen Datei persistent gespeichert, ist beim Deserialisieren die Reihenfolge
der abgelegten Datentypen zu beachten (vgl. Übung 22.4). Das beim Serialisieren
implizit festgelegte Protokoll muss eingehalten werden.
Auch wenn die Begriffe Serialisierung und Persistenz häufig im gleichen Kontext
verwendet werden, bezeichnen sie unterschiedliche Vorgänge: Serialisierung bedeutet das Zerlegen möglicherweise sehr komplex strukturierter Objekte in einen linearen Strom, Persistenz dagegen das dauerhafte Speichern von Objekten auf einem externen Datenträger. Persistenz wird zwar häufig dadurch erreicht, dass Objekte serialisiert in einen mit einer Datei verbundenen Strom geschrieben werden. Dies ist jedoch nicht der einzige Anwendungsbereich des Serialisierungskonzeptes. Wir werden später sehen, wie sich damit Objekte von einer Anwendung zur anderen, ja sogar über Rechnergrenzen hinweg übertragen lassen.
Herunterladen