Liste P: Programmieren mit Java WS 2001/2002 Prof. Dr. V. Turau FH Wiesbaden Kapitel 8: Serialisierbarkeit Folie 189 : Serialisierbarkeit Die Umwandlung eines komplexen Objektes in ein Byte-Array nennt man Serialisierung Serialisierte Objekte können zu einem späteren Zeitpunkt wieder reproduziert werden, dieser Vorgang heißt Deserialisierung Bei der Serialisierung werden alle Informationen über die Struktur des Objektes abgespeichert Die binäre Darstellung kann in einer Datei abgespeichert über ein Netzwerk verschickt werden Folie 190 : Serialisierbarkeit Die Serialisierung wurde in Java Version 1.1 eingeführt Die Serialisierung von Objekten hat viele Anwendungen: Verteilte Systeme (RMI, Java Enterprise Beans, CORBA) Persistente Speicherung von Objekten Das Serialisierungskonzept in Java stützt sich auf wenige Klassen und Methoden und lässt sich sehr einfach anwenden Folie 191 : Serialisierbarkeit (Beispiel) Instanz der Klasse Stack wird serialisiert und in einer Datei abgespeichert Stack s = new Stack(); s.push(...); ... FileOutputStream fos = new FileOutputStream("tmp.ser"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(s); oos.flush(); oos.close(); In einen ObjectOutputStream können beliebig viele Objekte reingeschrieben werden, sie müssen nur in der gleichen Reihenfolge wieder ausgelesen werden Folie 192 : Serialisierbarkeit (Beispiel) Die serialisierte Instanz der Klasse Stack wird aus der Datei ausgelesen und deserialisiert Stack s; FileInputStream fis = new FileInputStream("tmp.ser"); ObjectInputStream ois = new ObjectInputStream(fis); s = (Stack) ois.readObject(); ois.close(); Folie 193 : Das Interface Serializable Eine Klasse deren Instanzen serialisiert werden sollen, muss das Interface Serializable aus dem Paket java.io implementieren Das Interface Serializable enthält keine Methoden Nicht jede Instanzvariable eignet sich zum abspeichern (z.B. Instanzen der Klasse File) Instanzvariablen mit dem Schlüsselwort transient werden nicht serialisiert Folie 194 : Serialisierbarkeit Alle Instanzvariablen welche nicht das Schlüsselwort transient haben, werden serialisiert Die Serialisierung ist ein rekursiver Prozess, d.h. ist der Wert einer Instanzvariablen ein Objekt, so wird auch dieses serialisiert (d.h. im letzten Beispiel werden die Objekte im Stack ebenfalls alle serialisiert) Es wird ein Objekt-Graph gespeichert Dopppelt referenzierte Objekte werden nur einmal serialisiert Folie 195 : Komprimierte Speicherung Zur Reduktion des Speicheraufwandes können Objekte auch komprimiert gespeichert werden Beispiel: StringStack st = new StringStack(); ..... FileOutputStream fos = new FileOutputStream("tmp.ser.gz"); GZIPOutputStream gos = new GZIPOutputStream(fos); ObjectOutputStream oos = new ObjectOutputStream(gos); oos.writeObject(s); oos.flush(); oos.close(); Zum Einlesen wird die Klasse GZIPInputStream verwendet Folie 196 : Spezielle Anwendungen Der vorgestellte Defaultserialisierungssmechanismus erlaubt keinerlei Eingriffe in den Serialisierungsprozess Für manche Anwendungen ist dies nicht ausreichend Für diesen Fall gibt es zwei Alternativen: eine Klasse kann das Interface Externalizable implementieren der Defaultserialisierungssmechanismus kann beeinflusst werden Diese ermöglicht eine bessere Kontrolle des Serialisierungsprozesses Anwender tragen aber auch mehr Verantwortung Folie 197 : Das Interface Externalizable Das Interface Externalizable public interface Externalizable extends Serializable { public void writeExternal(ObjectOutput out) throws IOException; public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException; } Diese Interface sollte nur in speziellen Anwendungen verwendet werden Folie 198 : Angepasste Serialisierung Um von dem Defaultserialisierungssmechanismus abzuweichen, kann eine Klasse die Methoden private void writeObject(ObjectOutputStream oos) throws IOException; private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException; verwenden In diesen Methoden wird das Serialisierungverfahren implementiert Innerhalb dieser Methoden kann auf den Defaultmechanismus zurückgegriffen werden Folie 199 : Angepasste Serialisierung Hierzu stehen folgende Methoden public void defaultWriteObject() throws IOException public void defaultReadObject() throws IOException, ClassNotFoundException, NotActiveException aus den Klassen ObjectOutputStream und ObjectInputStream zur Verfügung. Vor und nach dem Aufruf dieser Methoden können spezielle Anpassungen vorgenommen werden Folie 200 : Beispiel Eine Klasse repräsentiert eine symmetrische Matrix, d.h. Werte oberhalb und unterhalb der Diagonalen sind identisch Beispiel: 1 4 7 15 4 3 -2 5 7 -2 -2 17 15 5 17 0 Beim Serialisieren genügt es, die obere "Hälfte" der Matrix zu speichern Folie 201 : Beispiel public class CustomDataExample implements Serializable { transient int dimension; transient int thearray[][]; private void writeObject(ObjectOutputStream s) throws IOException { // Für spätere Erweiterungen jetzt schon aufnehmen s.defaultWriteObject(); // Die Dimension wird gespeichert s.writeInt(dimension); // die obere "Hälfte" der Matrix wird gespeichert for (int i = 0; i < dimension; i++) { for (int j = 0; j <= i; j++) { s.writeInt(thearray[i][j]); } } } Folie 202 : Beispiel private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException // Für spätere Erweiterungen jetzt schon aufnehmen s.defaultReadObject(); // Die Dimension einlesen dimension = s.readInt(); // Speicherplatz allozieren thearray = new int[dimension][dimension]; // die obere Hälfte einlesen for (int i = 0; i < dimension; i++) { for (int j = 0; j <= i; j++) { thearray[i][j] = s.readInt(); } } // Den Rest rüberkopieren for (int i = 0; i < dimension; i++) { for (int j = dimension - 1; j > i; j--) { thearray[i][j] = thearray[j][i]; } } } Folie 203 : Versionsverwaltung { Probleme entstehen, wenn nach der Serialisierung einer Instanz einer Klasse der Quellcode der Klasse geändert und die Klasse neu übersetzt wird Jede Änderung einer Instanzvariablen oder der Signatur einer öffentlichen Methode führt zu Inkompatibilitäten Kleinere Änderungen der Klasse beeinflussen nicht die Deserialiserbarkeit Wie kann festgestellt werden, ob die Deserialiserbarkeit verloren geht? Folie 204 : Versionsverwaltung Lösung: Versionsnummern Das Programm serialver bestimmt eine Versionsnummer für eine Klasse (Aufruf mit serialver -show) Diese Versionsnummer muss in einer statischen Instanzvariablen mit dem Namen serialVersionUID vom Typ long abgespeichert werden static final long serialVersionUID = 1224463164541339165L; Folie 205 : Das Werkzeug serialver Bei kleineren Änderungen bleibt diese Nummer unverändert Das Werkzeug serialver erzeugt mittels des Secure Hash Algorithm (SHA) eine Versionsnummer D.h. wird nach einer Änderung der Klasse die gleiche Versionsnummer festgestellt, so ist eine Deserialiserung weiterhin möglich