From 33613a85afc4b1481367fbe92a17ee59c240250b Mon Sep 17 00:00:00 2001
From: Sven Eisenhauer
+Unter Serialisierung wollen wir die
+Fähigkeit verstehen, ein Objekt, das im Hauptspeicher der Anwendung
+existiert, in ein Format zu konvertieren, das es erlaubt, das Objekt
+in eine Datei zu schreiben oder über eine Netzwerkverbindung
+zu transportieren. Dabei wollen wir natürlich auch den umgekehrten
+Weg einschließen, also das Rekonstruieren eines in serialisierter
+Form vorliegenden Objekts in das interne Format der laufenden Java-Maschine.
+
+
+Serialisierung wird häufig mit dem Begriff Persistenz
+gleichgesetzt, vor allem in objektorientierten Programmiersprachen.
+Das ist nur bedingt richtig, denn Persistenz bezeichnet genaugenommen
+das dauerhafte Speichern von Daten auf einem externen Datenträger,
+so dass sie auch nach dem Beenden des Programms erhalten bleiben.
+Obwohl die persistente Speicherung von Objekten sicherlich eine der
+Hauptanwendungen der Serialisierung ist, ist sie nicht ihre einzige.
+Wir werden später Anwendungen sehen, bei der die Serialisierung
+von Objekten nicht zum Zweck ihrer persistenten Speicherung genutzt
+werden.
+Während es vor dem JDK 1.1 keine einheitliche Möglichkeit
+gab, Objekte zu serialisieren, gibt es seither im Paket java.io
+die Klasse ObjectOutputStream,
+mit der das sehr einfach zu realisieren ist. ObjectOutputStream
+besitzt einen Konstruktor, der einen OutputStream
+als Argument erwartet:
+
+
+Der an den Konstruktor übergebene OutputStream
+dient als Ziel der Ausgabe. Hier kann ein beliebiges Objekt der Klasse
+OutputStream
+oder einer daraus abgeleiteten Klasse übergeben werden. Typischerweise
+wird ein FileOutputStream
+verwendet, um die serialisierten Daten in eine Datei zu schreiben.
+
+
+ObjectOutputStream
+besitzt sowohl Methoden, um primitive Typen zu serialisieren, als
+auch die wichtige Methode writeObject,
+mit der ein komplettes Objekt serialisiert werden kann:
+
+
+
+
+
+
+Während die Methoden zum Schreiben der primitiven Typen ähnlich
+funktionieren wie die gleichnamigen Methoden der Klasse RandomAccessFile
+(siehe Abschnitt 20.4),
+ist die Funktionsweise von writeObject
+wesentlich komplexer. writeObject
+schreibt folgende Daten in den OutputStream:
+
+Insbesondere der letzte Punkt verdient dabei besondere Beachtung.
+Die Methode writeObject
+durchsucht also das übergebene Objekt nach Membervariablen und
+überprüft deren Attribute. Ist eine Membervariable vom Typ
+static,
+wird es nicht serialisiert, denn es gehört nicht zum Objekt,
+sondern zur Klasse des Objekts. Weiterhin werden alle Membervariablen
+ignoriert, die mit dem Schlüsselwort transient
+deklariert wurden. Auf diese Weise kann das Objekt Membervariablen
+definieren, die aufgrund ihrer Natur nicht serialisiert werden sollen
+oder dürfen. Wichtig ist weiterhin, dass ein Objekt nur dann
+mit writeObject
+serialisiert werden kann, wenn es das Interface Serializable
+implementiert.
+
+
+aufwändiger als auf den ersten Blick ersichtlich ist das Serialisieren
+von Objekten vor allem aus zwei Gründen:
+
+Wir wollen uns zunächst ein Beispiel ansehen. Dazu konstruieren
+wir eine einfache Klasse Time,
+die eine Uhrzeit, bestehend aus Stunden und Minuten, kapselt:
+
+
+
+
+
+
+ Titel
+ Inhalt
+ Suchen
+ Index
+ DOC
+ Handbuch der Java-Programmierung, 5. Auflage
+
+ <<
+ <
+ >
+ >>
+ API
+ Kapitel 41 - Serialisierung
+
+
+
+
+
+41.1 Grundlagen
+
+
+
+
+
+
+
+
+41.1.1 Begriffsbestimmung
+
+
+
+
+
+
+
+
+
+
+
+![]()
+
+
+
+![]()
+
+
+
+
+
+ Hinweis
+
+
41.1.2 Schreiben von Objekten
+
+
+
+
+
+
+
+
+
+
+public ObjectOutputStream(OutputStream out)
+ throws IOException
+
+
+
+java.io.ObjectOutputStream
+
+
+
+
+
+
+
+
+
+public final void writeObject(Object obj)
+ throws IOException
+public void writeBoolean(boolean data)
+ throws IOException
+public void writeByte(int data)
+ throws IOException
+public void writeShort(int data)
+ throws IOException
+public void writeChar(int data)
+ throws IOException
+public void writeInt(int data)
+ throws IOException
+public void writeLong(long data)
+ throws IOException
+public void writeFloat(float data)
+ throws IOException
+public void writeDouble(double data)
+ throws IOException
+public void writeBytes(String data)
+ throws IOException
+public void writeChars(String data)
+ throws IOException
+public void writeUTF(String data)
+ throws IOException
+
+
+
+java.io.ObjectOutputStream
+
+
+
+
+
+
+
+
+
+Listing 41.1: Eine serialisierbare Uhrzeitklasse
+
+
+
+
+
+001 /* Time.java */
+002
+003 import java.io.*;
+004
+005 public class Time
+006 implements Serializable
+007 {
+008 private int hour;
+009 private int minute;
+010
+011 public Time(int hour, int minute)
+012 {
+013 this.hour = hour;
+014 this.minute = minute;
+015 }
+016
+017 public String toString()
+018 {
+019 return hour + ":" + minute;
+020 }
+021 }
+
+
+Time.java
+
+Time besitzt einen öffentlichen +Konstruktor und eine toString-Methode +zur Ausgabe der Uhrzeit. Die Membervariablen hour +und minute wurden als private +deklariert und sind nach außen nicht sichtbar. Die Sichtbarkeit +einer Membervariable hat keinen Einfluss darauf, ob es von writeObject +serialisiert wird oder nicht. Mit Hilfe eines Objekts vom Typ ObjectOutputStream +kann ein Time-Objekt serialisiert +werden: + + +
+
+
+
+001 /* Listing4102.java */
+002
+003 import java.io.*;
+004 import java.util.*;
+005
+006 public class Listing4102
+007 {
+008 public static void main(String[] args)
+009 {
+010 try {
+011 FileOutputStream fs = new FileOutputStream("test1.ser");
+012 ObjectOutputStream os = new ObjectOutputStream(fs);
+013 Time time = new Time(10,20);
+014 os.writeObject(time);
+015 os.close();
+016 } catch (IOException e) {
+017 System.err.println(e.toString());
+018 }
+019 }
+020 }
+
+ |
++Listing4102.java | +
+Wir konstruieren zunächst einen FileOutputStream, +der das serialisierte Objekt in die Datei test1.ser +schreiben soll. Anschließend erzeugen wir einen ObjectOutputStream +durch Übergabe des FileOutputStream +an dessen Konstruktor. Nun wird ein Time-Objekt +für die Uhrzeit 10:20 konstruiert und mit writeObject +serialisiert. Nach dem Schließen des Streams steht das serialisierte +Objekt in »test1.ser«. +
+
![]() |
+
+
+ +Wichtig an der Deklaration von Time +ist das Implementieren des Serializable-Interfaces. +Zwar definiert Serializable +keine Methoden, writeObject +testet jedoch, ob das zu serialisierende Objekt dieses Interface implementiert. +Ist das nicht der Fall, wird eine Ausnahme des Typs NotSerializableException +ausgelöst. |
+
+
|
+![]() |
+
+Ein ObjectOutputStream +kann nicht nur ein Objekt serialisieren, sondern beliebig viele, sie +werden nacheinander in den zugrundeliegenden OutputStream +geschrieben. Das folgende Programm zeigt, wie zunächst ein int, +dann ein String +und schließlich zwei Time-Objekte +serialisiert werden: + + +
+
+
+
+001 /* Listing4103.java */
+002
+003 import java.io.*;
+004 import java.util.*;
+005
+006 public class Listing4103
+007 {
+008 public static void main(String[] args)
+009 {
+010 try {
+011 FileOutputStream fs = new FileOutputStream("test2.ser");
+012 ObjectOutputStream os = new ObjectOutputStream(fs);
+013 os.writeInt(123);
+014 os.writeObject("Hallo");
+015 os.writeObject(new Time(10, 30));
+016 os.writeObject(new Time(11, 25));
+017 os.close();
+018 } catch (IOException e) {
+019 System.err.println(e.toString());
+020 }
+021 }
+022 }
+
+ |
++Listing4103.java | +
+Da ein int +ein primitiver Typ ist, muss er mit writeInt +serialisiert werden. Bei den übrigen Aufrufen kann writeObject +verwendet werden, denn alle übergebenen Argumente sind Objekte. +
+
![]() |
+
+
+ +Es gibt keine verbindlichen Konventionen für die Benennung von +Dateien mit serialisierten Objekten. Die in den Beispielen verwendete +Erweiterung .ser ist allerdings recht +häufig zu finden, ebenso wie Dateierweiterungen des Typs .dat. +Wenn eine Anwendung viele unterschiedliche Dateien mit serialisierten +Objekten hält, kann es auch sinnvoll sein, die Namen nach dem +Typ der serialisierten Objekte zu vergeben. |
+
+
|
+![]() |
+
+Nachdem ein Objekt serialisiert wurde, kann es mit Hilfe der Klasse +ObjectInputStream +wieder rekonstruiert werden. Analog zu ObjectOutputStream +gibt es Methoden zum Wiedereinlesen von primitiven Typen und eine +Methode readObject, +mit der ein serialisiertes Objekt wieder hergestellt werden kann: + + + +
+
+
++public final Object readObject() + throws OptionalDataException, + ClassNotFoundException, + IOException +public boolean readBoolean() + throws IOException +public byte readByte() + throws IOException +public short readShort() + throws IOException +public char readChar() + throws IOException +public int readInt() + throws IOException +public long readLong() + throws IOException +public float readFloat() + throws IOException +public double readDouble() + throws IOException +public String readUTF() + throws IOException ++ + |
++java.io.ObjectInputStream | +
+Zudem besitzt die Klasse ObjectInputStream +einen Konstruktor, der einen InputStream +als Argument erwartet, der zum Einlesen der serialisierten Objekte +verwendet wird: +
+
+
++public ObjectInputStream(InputStream in) ++ + |
++java.io.ObjectInputStream | +
+Das Deserialisieren eines Objektes kann man sich stark vereinfacht +aus den folgenden beiden Schritten bestehend vorstellen: +
+Das erzeugte Objekt hat anschließend dieselbe Struktur und denselben +Zustand, den das serialisierte Objekt hatte (abgesehen von den nicht +serialisierten Membervariablen des Typs static +oder transient). +Da der Rückgabewert von readObject +vom Typ Object +ist, muss das erzeugte Objekt in den tatsächlichen Typ (oder +eine seiner Oberklassen) umgewandelt werden. Das folgende Programm +zeigt das Deserialisieren am Beispiel des in Listing 41.2 +serialisierten und in die Datei test1.ser +geschriebenen Time-Objekts: + + +
+
+
+
+001 /* Listing4104.java */
+002
+003 import java.io.*;
+004 import java.util.*;
+005
+006 public class Listing4104
+007 {
+008 public static void main(String[] args)
+009 {
+010 try {
+011 FileInputStream fs = new FileInputStream("test1.ser");
+012 ObjectInputStream is = new ObjectInputStream(fs);
+013 Time time = (Time)is.readObject();
+014 System.out.println(time.toString());
+015 is.close();
+016 } catch (ClassNotFoundException e) {
+017 System.err.println(e.toString());
+018 } catch (IOException e) {
+019 System.err.println(e.toString());
+020 }
+021 }
+022 }
+
+ |
++Listing4104.java | +
+Hier wird zunächst ein FileInputStream
+für die Datei test1.ser geöffnet
+und an den Konstruktor des ObjectInputStream-Objekts
+is übergeben. Alle lesenden
+Aufrufe von is beschaffen ihre
+Daten damit aus test1.ser. Jeder Aufruf
+von readObject
+liest immer das nächste gespeicherte Objekt aus dem Eingabestream.
+Das Programm zum Deserialisieren muss also genau wissen, welche Objekttypen
+in welcher Reihenfolge serialisiert wurden, um sie erfolgreich deserialisieren
+zu können. In unserem Beispiel ist die Entscheidung einfach,
+denn in der Eingabedatei steht nur ein einziges Time-Objekt.
+readObject
+deserialisiert es und liefert ein neu erzeugtes Time-Objekt,
+dessen Membervariablen mit den Werten aus dem serialisierten Objekt
+belegt werden. Die Ausgabe des Programms ist demnach:
+
+
+10:20
+
+
+
+
![]() |
+![]() |
+
+
+ +Es ist wichtig zu verstehen, dass beim Deserialisieren nicht der Konstruktor +des erzeugten Objekts aufgerufen wird. Lediglich bei einer serialisierbaren +Klasse, die in ihrer Vererbungshierarchie Superklassen hat, die nicht +das Interface Serializable +implementieren, wird der parameterlose Konstruktor der nächsthöheren +nicht-serialisierbaren Vaterklasse aufgerufen. Da die aus der nicht-serialisierbaren +Vaterklasse geerbten Membervariablen nicht serialisiert werden, soll +auf diese Weise sichergestellt sein, dass sie wenigstens sinnvoll +initialisiert werden. + +
+Auch eventuell vorhandene Initialisierungen einzelner Membervariablen
+werden nicht ausgeführt. Wir könnten beispielsweise die
+Time-Klasse aus Listing 41.1
+um eine Membervariable seconds
+erweitern:
+
+ +Dann wäre zwar bei allen mit new +konstruierten Objekten der Sekundenwert mit 11 vorbelegt. Bei Objekten, +die durch Deserialisieren erzeugt wurden, bleibt er aber 0 (das ist +der Standardwert eines int, +siehe Tabelle 4.1), +denn der Initialisierungscode wird in diesem Fall nicht ausgeführt. |
+
+
|
+![]() |
+
+Beim Deserialisieren von Objekten können einige Fehler passieren. +Damit ein Aufruf von readObject +erfolgreich ist, müssen mehrere Kriterien erfüllt sein: +
+Soll beispielsweise die in Listing 41.3 +erzeugte Datei test2.ser deserialisiert +werden, so müssen die Aufrufe der read-Methoden +in Typ und Reihenfolge denen des serialisierenden Programms entsprechen: + + +
+
+
+
+001 /* Listing4105.java */
+002
+003 import java.io.*;
+004 import java.util.*;
+005
+006 public class Listing4105
+007 {
+008 public static void main(String[] args)
+009 {
+010 try {
+011 FileInputStream fs = new FileInputStream("test2.ser");
+012 ObjectInputStream is = new ObjectInputStream(fs);
+013 System.out.println("" + is.readInt());
+014 System.out.println((String)is.readObject());
+015 Time time = (Time)is.readObject();
+016 System.out.println(time.toString());
+017 time = (Time)is.readObject();
+018 System.out.println(time.toString());
+019 is.close();
+020 } catch (ClassNotFoundException e) {
+021 System.err.println(e.toString());
+022 } catch (IOException e) {
+023 System.err.println(e.toString());
+024 }
+025 }
+026 }
+
+ |
++Listing4105.java | +
+Das Programm rekonstruiert alle serialisierten Elemente aus »test2.ser«.
+Seine Ausgabe ist:
+
+
+123
+Hallo
+10:30
+11:25
+
+
+
| Titel + | Inhalt + | Suchen + | Index + | DOC + | Handbuch der Java-Programmierung, 5. Auflage, Addison +Wesley, Version 5.0.1 + |
| << + | < + | > + | >> + | API + | © 1998, 2007 Guido Krüger & Thomas +Stark, http://www.javabuch.de + |