Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.
 
Inhaltsverzeichnis
Vorwort
1 Neues in Java 8 und Java 7
2 Fortgeschrittene String-Verarbeitung
3 Threads und nebenläufige Programmierung
4 Datenstrukturen und Algorithmen
5 Raum und Zeit
6 Dateien, Verzeichnisse und Dateizugriffe
7 Datenströme
8 Die eXtensible Markup Language (XML)
9 Dateiformate
10 Grafische Oberflächen mit Swing
11 Grafikprogrammierung
12 JavaFX
13 Netzwerkprogrammierung
14 Verteilte Programmierung mit RMI
15 RESTful und SOAP-Web-Services
16 Technologien für die Infrastruktur
17 Typen, Reflection und Annotationen
18 Dynamische Übersetzung und Skriptsprachen
19 Logging und Monitoring
20 Sicherheitskonzepte
21 Datenbankmanagement mit JDBC
22 Java Native Interface (JNI)
23 Dienstprogramme für die Java-Umgebung
Stichwortverzeichnis

Jetzt Buch bestellen
Ihre Meinung?

Spacer
<< zurück
Java SE 8 Standard-Bibliothek von Christian Ullenboom
Das Handbuch für Java-Entwickler
Buch: Java SE 8 Standard-Bibliothek

Java SE 8 Standard-Bibliothek
Pfeil 7 Datenströme
Pfeil 7.1 Stream-Klassen für Bytes und Zeichen
Pfeil 7.1.1 Lesen aus Dateien und Schreiben in Dateien
Pfeil 7.1.2 Byteorientierte Datenströme über Files beziehen
Pfeil 7.1.3 Zeichenorientierte Datenströme über Files beziehen
Pfeil 7.1.4 Funktion von OpenOption bei den Files.newXXX(…)-Methoden
Pfeil 7.1.5 Ressourcen aus dem Klassenpfad und aus JAR‐Archiven laden
Pfeil 7.1.6 Die Schnittstellen Closeable, AutoCloseable und Flushable
Pfeil 7.2 Basisklassen für die Ein-/Ausgabe
Pfeil 7.2.1 Die abstrakten Basisklassen
Pfeil 7.2.2 Übersicht über Ein-/Ausgabeklassen
Pfeil 7.2.3 Die abstrakte Basisklasse OutputStream
Pfeil 7.2.4 Ein Datenschlucker *
Pfeil 7.2.5 Die abstrakte Basisklasse InputStream
Pfeil 7.2.6 Ströme mit SequenceInputStream zusammensetzen *
Pfeil 7.2.7 Die abstrakte Basisklasse Writer
Pfeil 7.2.8 Die Schnittstelle Appendable *
Pfeil 7.2.9 Die abstrakte Basisklasse Reader
Pfeil 7.3 Formatierte Textausgaben
Pfeil 7.3.1 Die Klassen PrintWriter und PrintStream
Pfeil 7.3.2 System.out, System.err und System.in
Pfeil 7.4 Die FileXXX-Stromklassen
Pfeil 7.4.1 Kopieren mit FileOutputStream und FileInputStream
Pfeil 7.4.2 Das FileDescriptor-Objekt *
Pfeil 7.4.3 Mit dem FileWriter Texte in Dateien schreiben
Pfeil 7.4.4 Zeichen mit der Klasse FileReader lesen
Pfeil 7.5 Schreiben und Lesen aus Strings und Byte-Feldern
Pfeil 7.5.1 Mit dem StringWriter ein String-Objekt füllen
Pfeil 7.5.2 CharArrayWriter
Pfeil 7.5.3 StringReader und CharArrayReader
Pfeil 7.5.4 Mit ByteArrayOutputStream in ein Byte-Feld schreiben
Pfeil 7.5.5 Mit ByteArrayInputStream aus einem Byte-Feld lesen
Pfeil 7.6 Datenströme filtern und verketten
Pfeil 7.6.1 Streams als Filter verketten (verschachteln)
Pfeil 7.6.2 Gepufferte Ausgaben mit BufferedWriter und BufferedOutputStream
Pfeil 7.6.3 Gepufferte Eingaben mit BufferedReader/BufferedInputStream
Pfeil 7.6.4 LineNumberReader zählt automatisch Zeilen mit *
Pfeil 7.6.5 Daten mit der Klasse PushbackReader zurücklegen *
Pfeil 7.6.6 DataOutputStream/DataInputStream *
Pfeil 7.6.7 Basisklassen für Filter *
Pfeil 7.6.8 Die Basisklasse FilterWriter *
Pfeil 7.6.9 Ein LowerCaseWriter *
Pfeil 7.6.10 Eingaben mit der Klasse FilterReader filtern *
Pfeil 7.6.11 Anwendungen für FilterReader und FilterWriter *
Pfeil 7.7 Vermittler zwischen Byte-Streams und Unicode-Strömen
Pfeil 7.7.1 Datenkonvertierung durch den OutputStreamWriter
Pfeil 7.7.2 Automatische Konvertierungen mit dem InputStreamReader
Pfeil 7.8 Kommunikation zwischen Threads mit Pipes *
Pfeil 7.8.1 PipedOutputStream und PipedInputStream
Pfeil 7.8.2 PipedWriter und PipedReader
Pfeil 7.9 Prüfsummen
Pfeil 7.9.1 Die Schnittstelle Checksum
Pfeil 7.9.2 Die Klasse CRC32
Pfeil 7.9.3 Die Adler32-Klasse
Pfeil 7.10 Persistente Objekte und Serialisierung
Pfeil 7.10.1 Objekte mit der Standardserialisierung speichern und lesen
Pfeil 7.10.2 Zwei einfache Anwendungen der Serialisierung *
Pfeil 7.10.3 Die Schnittstelle Serializable
Pfeil 7.10.4 Nicht serialisierbare Attribute aussparen
Pfeil 7.10.5 Das Abspeichern selbst in die Hand nehmen
Pfeil 7.10.6 Tiefe Objektkopien *
Pfeil 7.10.7 Versionenverwaltung und die SUID
Pfeil 7.10.8 Wie die ArrayList serialisiert *
Pfeil 7.10.9 Probleme mit der Serialisierung
Pfeil 7.11 Alternative Datenaustauschformate
Pfeil 7.11.1 Serialisieren in XML-Dateien
Pfeil 7.11.2 XML-Serialisierung von JavaBeans mit JavaBeans Persistence *
Pfeil 7.11.3 Die Open-Source-Bibliothek XStream *
Pfeil 7.11.4 Binäre Serialisierung mit Google Protocol Buffers *
Pfeil 7.12 Zum Weiterlesen
 
Zum Seitenanfang

7.10Persistente Objekte und Serialisierung Zur vorigen ÜberschriftZur nächsten Überschrift

Objekte liegen zwar immer nur zur Laufzeit vor, doch auch nach dem Beenden der virtuellen Maschine soll ihre Struktur nicht verloren gehen. Gewünscht ist ein Mechanismus, der die Objektstruktur und Variablenbelegung zu einer bestimmten Zeit sicher (persistent) macht und an anderer Stelle wieder hervorholt und die Objektstruktur und Variablenbelegung restauriert. Im gespeicherten Datenformat müssen alle Informationen wie der Objekttyp und der Variablentyp enthalten sein, um später das richtige Wiederherstellen zu ermöglichen. Da Objekte oftmals weitere Objekte einschließen, müssen auch diese Unterobjekte gesichert werden (schreibe ich eine Liste mit Bestellungen, so ist die Liste ohne die referenzierten Objekte sinnlos). Genau dieser Mechanismus wird auch dann angewendet, wenn Objekte über das Netzwerk schwirren.[ 82 ](Die Rede ist hier von RMI.) Die persistenten Objekte sichern also neben ihren eigenen Informationen auch die Unterobjekte – also die von der betrachtenden Stelle aus erreichbaren. Beim Speichern wird rekursiv ein Objektbaum durchlaufen, um eine vollständige Datenstruktur zu erhalten. Der doppelte Zugriff auf ein Objekt wird hier ebenso beachtet wie der Fall, dass zyklische Abhängigkeiten auftreten. Jedes Objekt bekommt dabei ein Handle, sodass es im Datenstrom nur einmal kodiert wird.

Unter Java SE lassen sich Objekte über verschiedene Ansätze automatisch persistent abbilden und speichern:

  • Standardserialisierung: Die Objektstruktur und Zustände werden in einem binären Format gesichert. Das Verfahren wird auch Java Object Serialization (JOS) genannt – der Punkt, mit dem wir uns im Folgenden beschäftigen wollen. Die Standardserialisierung ist bei entfernten Methodenaufrufen sehr wichtig und weniger, um Dinge über einen langen Zeitraum abzuspeichern und dann irgendwann einmal wieder aus dem Schrank zu holen.

  • XML-Serialisierung über JavaBeans Persistence: JavaBeans – und nur solche – können wir in einem XML-Format sichern. Eine Lösung ist die JavaBeans Persistence (JBP), die ursprünglich für Swing gedacht war. Denn wenn der Zustand einer grafischen Oberfläche mit JOS binär persistiert wird, sind Änderungen an den Interna der Swing-API nicht so einfach möglich, da das Binärformat der JOS sehr eng mit dem Objektmodell verbunden ist. Das heißt, Objekte lassen sich mitunter nicht mehr aus dem Binärdokument rekonstruieren. JBP entkoppelt das, indem nur über Setter/Getter kommuniziert wird und nicht auf internen Referenzen, die ein Implementierungsdetail sind, das sich jederzeit ändern kann. Heutzutage spielt JBP in der Praxis kaum eine Rolle.

  • XML-Abbildung über JAXB: Mit JAXB steht eine zweite API zum Abbilden der Objektstruktur auf XML-Dokumente bereit. JAXB ist Teil der Standardbibliothek ab Version 6 und wird in Kapitel 8, »Die eXtensible Markup Language (XML)«, erklärt. Sie ist eine sehr wichtige Technologie, insbesondere für Web-Service-Aufrufe.

Die drei Möglichkeiten JOS, JBP und JAXB sind in Java SE schon eingebaut. Die Standardserialisierung erzeugt ein binäres Format und ist sehr stark auf Java ausgerichtet, sodass andere Systeme nicht viel mit den Daten anfangen können. XML ist als Format praktisch, da es auch von anderen Systemen verarbeitet werden kann. Ein anderes kompaktes Binärformat, das auch Interoperabilität erlaubt, ist Protocol Buffers (http://code.google.com/p/protobuf/) von Google; das Unternehmen setzt es intern ein, wenn unterschiedliche Anwendungen Daten austauschen sollen. Und auch bei Apple sind Protocol Buffers im Einsatz, etwa bei den iCloud-Diensten.

Etwas weiter gedacht lassen sich auch Objekte in relationalen Datenbanken speichern, was sich objektrelationales Mapping(OR-Mapping) nennt. Das ist sehr anspruchsvoll, da die Objektmodelle und Tabellen so ganz anders sind. Die Java SE bietet keine Unterstützung für das OR-Mapping an, doch mit zusätzlichen Frameworks, wie der JPA (Java Persistence API) aus der Java Platform, Enterprise Edition, ist das zu schaffen. Auch von Hand können die Objekte über JDBC in die Datenbank gebracht werden, was aber nicht zeitgemäß ist.

 
Zum Seitenanfang

7.10.1Objekte mit der Standardserialisierung speichern und lesen Zur vorigen ÜberschriftZur nächsten Überschrift

Die Standardserialisierung bietet eine einfache Möglichkeit, um Objekte persistent zu machen und später wiederherzustellen. Dabei werden die Objektzustände (keine statischen!) in einen Byte-Strom geschrieben (Serialisierung), woraus sie später wieder zu einem Objekt rekonstruiert werden können (Deserialisierung). Im Zentrum stehen zwei Klassen und ihre (De-)Serialisierungsmethoden:

  • Serialisierung: Die Klasse ObjectOutputStream und die Methode writeObject(Object). Während der Serialisierung geht der ObjectOutputStream die Zustände und Objektverweise rekursiv ab und schreibt die Zustände Schritt für Schritt in einen Ausgabestrom.

  • Deserialisierung: Dem Lesen der serialisierten Objektzustände dient die Klasse ObjectInputStream. Ihre Methode readObject() findet den Typ des serialisierten Objekts und baut daraus zur Laufzeit das Zielobjekt auf.

ObjectOutputStream

An einem Beispiel lässt sich gut erkennen, wie ein ObjectOutputStream einen String und das aktuelle Tagesdatum in einen OutputStream speichert. Um die Daten in eine Datei zu holen, ist der OutputStream ein FileOutputStream für eine Datei datum.ser. Der Dateiname wird meist so gewählt, dass er mit .ser endet:

Listing 7.30com/tutego/insel/io/ser/SerializeNameAndDate.java, main()

try ( OutputStream fos = Files.newOutputStream( Paths.get( "name_date.ser" ) );
ObjectOutputStream oos = new ObjectOutputStream( fos ) ) {
oos.writeObject( "Chris" );
oos.writeObject( new Date() );
}
catch ( IOException e ) {
System.err.println( e );
}

Allen Anfang bildet wie üblich ein OutputStream, der die Zustände der Objekte und Meta-Informationen aufnimmt. In unserem Fall ist das der FileOutputStream. Die Verbindung zwischen der Datei und dem Objektstrom durch die Klasse ObjectOutputStream geschieht über den Konstruktor, der einen OutputStream annimmt. ObjectOutputStream implementiert die Schnittstelle ObjectOutput und bietet so beispielsweise die Methode writeObject(Object) zum Schreiben von Objekten. Damit wird das Serialisieren des String-Objekts (der Name) und des anschließenden Datum-Objekts zum Kinderspiel.

class java.io.ObjectOutputStream
extends OutputStream
implements ObjectOutput, ObjectStreamConstants
  • ObjectOutputStream(OutputStream out) throws IOException
    Erzeugt einen ObjectOutputStream, der in den angegebenen OutputStream schreibt. Ein Fehler kann von den Methoden aus dem OutputStream kommen.

  • final void writeObject(Object obj) throws IOException
    Schreibt das Objekt.

  • void flush() throws IOException
    Schreibt noch gepufferte Daten.

  • void close() throws IOException
    Schließt den Datenstrom. Die Methode muss aufgerufen werden, bevor der Datenstrom zur Eingabe verwendet werden soll.

Die Methode writeObject(Object) kann nicht nur bei Ein-/Ausgabefehlern eine IOException auslösen, sondern auch eine NotSerializableException, wenn das Objekt gar nicht serialisierbar ist, und eine InvalidClassException, wenn beim Serialisieren etwas falsch läuft. Die Typen NotSerializableException und InvalidClassException sind Unterklassen von IOException, sodass ein catch(IOException) diese Typen gleich mit fängt.

Objekte über die Standardserialisierung lesen

Aus den Daten im Datenstrom stellt der ObjectInputStream ein neues Objekt her und initialisiert die Zustände, wie sie geschrieben wurden. Wenn nötig, restauriert der ObjectInputStream auch Objekte, auf die verwiesen wurde. Die Klasseninformationen müssen zur Laufzeit vorhanden sein, weil bei der Serialisierung nur die Zustände, aber keine .class-Dateien gesichert werden. Während des Lesens findet readObject() also bei unserem Beispiel den String und das Datum. Der ObjectInputStream erwartet die Rohdaten wie üblich über einen Eingabestrom. Kommen die Informationen aus einer Datei, verwenden wir den FileInputStream:

Listing 7.31com/tutego/insel/io/ser/DeSerializeNameAndDate.java, main()

try ( InputStream fis = Files.newInputStream( Paths.get( "name_date.ser" ) );
ObjectInputStream ois = new ObjectInputStream( fis ) ) {
String name = (String) ois.readObject();
Date date = (Date) ois.readObject();

System.out.println( name );
System.out.println( date );
}
catch ( IOException | ClassNotFoundException e ) {
e.printStackTrace();
}

Die explizite Typumwandlung kann natürlich bei einer falschen Zuweisung zu einem Fehler führen. Insbesondere bei generischen Typen ist diese Typanpassung immer etwas lästig.

class java.io.ObjectInputStream
extends InputStream
implements ObjectInput, ObjectStreamConstants
  • ObjectInputStream(InputStream out) throws IOException
    Erzeugt einen ObjectInputStream, der aus einem gegebenen InputStream liest.

  • final Object readObject() throws ClassNotFoundException, IOException
    Liest ein Object und gibt es zurück. Eine ClassNotFoundException wird ausgelöst, wenn das Objekt zu einer Klasse gehört, die nicht auffindbar ist.

Die Schnittstellen ObjectOutput und ObjectInput *

Die Klasse ObjectOutputStream bekommt die Vorgabe für writeObject(Object) aus einer Schnittstelle ObjectOutput, genauso wie ObjectInputStream die Operation readObject() aus ObjectInput implementiert. Bis auf die Standardserialisierung haben die Schnittstellen ObjectOutput und ObjectInput in Java keine weitere Verwendung.

Die Schnittstelle ObjectOutput erweitert selbst die Schnittstelle DataOutput um das Schreiben von Primitiven: write(byte[]), write(byte[], int, int), write(int), writeBoolean(boolean), writeByte(int), writeBytes(String), writeChar(int), writeChars(String), writeDouble(double), writeFloat(float), writeInt(int), writeLong(long), writeShort(int) und writeUTF(String). Das ist bei einer eigenen angepassten Serialisierung interessant, wenn wir selbst das Schreiben von Zuständen übernehmen. Umgekehrt erweitert die Schnittstelle ObjectInput die Schnittstelle DataInput und bietet diverse readXXX(…)-Methoden.

 
Zum Seitenanfang

7.10.2Zwei einfache Anwendungen der Serialisierung * Zur vorigen ÜberschriftZur nächsten Überschrift

Im Folgenden wollen wir uns zwei Beispiele für die Serialisierung anschauen:

  • Objektzustände zu verpacken, ist bei der Kommunikation über ein Netzwerk sehr sinnvoll. Die Serialisierung kann einfach die Zustände von einem Rechner auf den anderen übertragen.

  • Serialisierung ist aber auch eine Möglichkeit, die Zustände als Byte-Feld etwa in eine Datenbank zu schreiben. Dabei werden wir sehen, dass der ByteArrayOutputStream eine nützliche Stream-Klasse ist.

Objekte über das Netzwerk schicken

Es ist natürlich wieder feines objektorientiertes Design, dass es der Methode writeObject(Object) egal ist, wohin das Objekt geschoben wird. Dazu wird ja einfach dem Konstruktor von ObjectOutputStream ein OutputStream übergeben, und writeObject(Object) delegiert dann das Senden der entsprechenden Einträge an die passenden Methoden der Output-Klasse. Im Beispiel SerializeAndDeserializeDate haben wir ein FileOutputStream benutzt.

Es gibt aber noch eine ganze Menge anderer Klassen, die vom Typ OutputStream sind. So können die Objekte auch in einer Datenbank abgelegt bzw. über das Netzwerk verschickt werden.

[zB]Beispiel

Öffne eine Netzwerkverbindung zum Server host unter dem Port port, und sende Objekt object:

try ( Socket socket = new Socket( host, port );
OutputStream os = socket.getOutputStream();
ObjectOutputStream oos = new ObjectOutputStream( os ) ) {
oos.writeObject( object );
}

Über s.getOutputStream() gelangen wir an den Datenstrom. Dann sieht alles wie gewohnt aus. Da wir allerdings auf der Empfängerseite noch ein Protokoll ausmachen müssen, verfolgen wir diesen Weg der Objektversendung nicht weiter und verlassen uns vielmehr auf eine Technik, die sich RMI nennt.

Objekte in ein Byte-Feld schreiben

Die Klassen ObjectOutputStream und ByteArrayOutputStream sind zusammen zwei gute Partner, wenn es darum geht, eine Repräsentation eines Objekts im Speicher zu erzeugen. So lässt sich auch die geschätzte Größe eines Objekts herauszufinden.

[zB]Beispiel

Wie viele Bytes umfasst eine serialisierte Liste mit zwei Punkten?

Object object = Arrays.asList( new Point( 0, 0 ), new Point( 1, 2 ) );
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try ( ObjectOutputStream oos = new ObjectOutputStream( baos ) ) {
oos.writeObject( object );
}
byte[] array = baos.toByteArray();
System.out.println( array.length ); // 172
 
Zum Seitenanfang

7.10.3Die Schnittstelle Serializable Zur vorigen ÜberschriftZur nächsten Überschrift

Bisher nahmen wir immer an, dass eine Klasse weiß, wie sie geschrieben wird. Das funktioniert wie selbstverständlich bei vielen vorhandenen Klassen, und so müssen wir uns bei writeObject(new Date()) keine Gedanken darüber machen, wie die Bibliothek das Datum schreibt und auch wieder liest.

Damit Objekte serialisiert werden können, müssen die Klassen die Schnittstelle Serializable implementieren. Diese Schnittstelle enthält keine Methoden und ist nur eine Markierungsschnittstelle (engl. marker interface). Implementiert eine Klasse diese Schnittstelle nicht, folgt beim Serialisierungsversuch eine NotSerializableException. Eine Klasse wie java.util.Date implementiert somit Serializable, Thread jedoch nicht. Der Serialisierer lässt damit alle Klassen »durch«, die instanceof Serializable sind. Daraus folgt, dass alle Unterklassen einer Klasse, die serialisierbar ist, auch ihrerseits serialisierbar sind. So implementiert java.lang.Number – die Basisklasse der Wrapper-Klassen – die Schnittstelle Serializable, und die konkreten Wrapper-Klassen wie Integer, BigDecimal sind somit ebenfalls serialisierbar.

[»]Hinweis

Werden Exemplare einer nichtstatischen inneren Klasse serialisiert, ohne dass die äußere Klasse Serializable implementiert, gibt es einen Fehler, denn intern hält ein Objekt der inneren Klasse einen Verweis auf das Exemplar der äußeren Klasse. Statische innere Klassen machen das nicht, was das Problem mit der Serialisierung lösen kann. Das Datenvolumen kann natürlich groß werden, wenn schlanke, nichtstatische innere Serializable-Klassen in einer äußeren Serializable-Klasse liegen, die sehr viele Eigenschaften besitzt.

Die serialisierbare Klasse Person

Wir wollen im Folgenden eine Klasse Person serialisierbar machen. Dazu benötigen wir das folgende Gerüst:

Listing 7.32com/tutego/insel/io/ser/Person.java

package com.tutego.insel.io.ser;

import java.io.Serializable;
import java.util.Date;

public class Person implements Serializable {

static int BMI_OVERWEIGHT = 25;

String name;
Date birthday;
double bodyHeight;
}

Erzeugen wir ein Person-Objekt p und rufen writeObject(p) auf, so schiebt der ObjectOutputStream die Variablenbelegungen (hier name, birthday und bodyHeight) in den Datenstrom.

Statische Variablen wie BMI_OVERWEIGHT werden nicht mit dem Standardserialisierungs-Mechanismus gesichert. Bevor durch Deserialisierung ein Objekt einer Klasse erzeugt wird, muss schon die Klasse geladen sein, was bedeutet, dass statische Variablen schon initialisiert sind. Wenn zwei Objekte wieder deserialisiert werden, könnte es andernfalls vorkommen, dass beide unterschiedliche Werte aufweisen. Was sollte dann passieren?

[»]Hinweis

Feld-Objekte sind standardmäßig serialisierbar – sie implementieren versteckt die Schnittstelle Serializable.

Nicht serialisierbare Objekte

Nicht alle Objekte sind serialisierbar. Zu den nicht serialisierbaren Klassen gehören zum Beispiel Thread und Socket und viele weitere Klassen aus dem java.io-Paket. Das liegt daran, dass nicht klar ist, wie zum Beispiel ein Wiederaufbau aussehen sollte. Wenn ein Thread etwa eine Datei zum Lesen geöffnet hat, wie soll der Zustand serialisiert werden, sodass er beim Deserialisieren auf einem anderen Rechner sofort wieder laufen und dort weitermachen kann, wo er mit dem Lesen aufgehört hat?

Ob Objekte als Träger sensibler Daten serialisierbar sein sollen, ist gut zu überlegen. Denn bei der Serialisierung der Zustände – es werden auch private Attribute serialisiert, an die zunächst nicht so einfach heranzukommen ist – öffnet sich die Kapselung. Aus dem Datenstrom lassen sich die internen Belegungen ablesen und auch manipulieren.

 
Zum Seitenanfang

7.10.4Nicht serialisierbare Attribute aussparen Zur vorigen ÜberschriftZur nächsten Überschrift

Es gibt eine Reihe von Objekttypen, die sich nicht serialisieren lassen – technisch gesprochen implementieren diese Klassen die Schnittstelle Serializable nicht. Der Grund, dass nicht alle Klassen diese Schnittstelle implementieren, kann zum Beispiel die Sicherheit sein: Ein Objekt, das Passwörter speichert, soll nicht einfach geschrieben werden. Da reicht es nicht, dass die Attribute privat sind, denn auch sie werden geschrieben. Der andere Punkt ist die Tatsache, dass sich nicht alle Zustände beim Deserialisieren wiederherstellen lassen. Was ist, wenn ein FileInputStream oder Thread serialisiert wird? Soll dann bei der Deserialisierung eine Datei geöffnet werden oder der Thread neu starten? Was ist, wenn die Datei nicht vorhanden ist? Da all diese Fragen ungeklärt sind, ist es am einfachsten, wenn die Klassen nicht serialisierbarer Objekte die Schnittstelle Serializable nicht implementieren.

Doch was soll geschehen, wenn ein Objekt geschrieben wird, das intern auf ein nicht serialisierbares Objekt – etwa auf einen Thread – verweist?

Die Serialisierung der folgenden Klasse führt zu einem Laufzeitfehler:

Listing 7.33com/tutego/insel/io/ser/SerializeTransient.java, NotTransientNotSerializable

class NotTransientNotSerializable implements Serializable {
Thread t = new Thread();
// transient Thread t = new Thread();
String s = "Fremde sind Freunde, die man nur noch nicht kennengelernt hat.";
}

Der Fehler wird eine NotSerializableException sein:

java.io.NotSerializableException: java.lang.Thread
at java.io.ObjectOutputStream.writeObject0(Unknown Source)
at java.io.ObjectOutputStream.defaultWriteFields(Unknown Source)
at java.io.ObjectOutputStream.writeSerialData(Unknown Source)
at java.io.ObjectOutputStream.writeOrdinaryObject(Unknown Source)
at java.io.ObjectOutputStream.writeObject0(Unknown Source)
at java.io.ObjectOutputStream.writeObject(Unknown Source)
at com.tutego.insel.io.ser.SerializeTransient.main(SerializeTransient.java:20)

Die Begründung dafür ist einfach: Ein Thread lässt sich nicht serialisieren.

Wollten wir ein Objekt vom Typ NotTransientNotSerializable ohne Thread serialisieren, müssen wir dem Serialisierungsmechanismus mitteilen: »Nimm so weit alle Objekte, aber nicht den Thread!«

Um Elemente bei der Serialisierung auszusparen, bietet Java zwei Möglichkeiten:

  • ein spezielles Schlüsselwort: transient

  • das Feld private final ObjectStreamField[] serialPersistentFields = {...}, das alle serialisierbaren Eigenschaften aufzählt

Statische Eigenschaften würden auch nicht serialisiert, aber das ist hier nicht unser Ziel.

[»]Hinweis

Ausnahmen sind standardmäßig serialisierbar, da Throwable die Schnittstelle Serializable implementiert. Denn gibt es Serverfehler bei entfernten Methodenaufrufen, so werden die Fehler gerne mit über die Leitung übertragen. Natürlich darf in dem Fall die zu serialisierende Ausnahme auch nur serialisierbare Attribute referenzieren.

Das Schlüsselwort transient

Um beim Serialisieren Attribute auszusparen, bietet Java den Modifizierer transient, der alle Attribute markiert, die nicht persistent sein sollen. Damit lassen wir die nicht serialisierbaren Kandidaten außen vor und speichern alles ab, was sich speichern lässt.

[zB]Beispiel

Das Thread-Objekt hinter t soll nicht serialisiert werden:

transient Thread t;

Die Variable serialPersistentFields *

Erkennt der Serialisierer in der Klasse eine private statische Feldvariable serialPersistentFields, wird er die ObjectStreamField-Einträge des Feldes beachten und nur die dort aufgezählten Elemente serialisieren, egal, was als transient markiert ist.

[zB]Beispiel

Von einer Klasse sollen nur der String name und das Datum date serialisiert werden:

private static final ObjectStreamField[] serialPersistentFields {
new ObjectStreamField( "name", String.class ),
new ObjectStreamField( "date", Date.class )
};
 
Zum Seitenanfang

7.10.5Das Abspeichern selbst in die Hand nehmen Zur vorigen ÜberschriftZur nächsten Überschrift

Die Java-Bibliothek realisiert intern ein Serialisierungsprotokoll, das beschreibt, wie die Abbildung auf einen Bytestrom aussieht. Dieses Object Serialization Stream Protocol beschreibt Oracle unter http://docs.oracle.com/javase/8/docs/platform/serialization/spec/protocol.html etwas genauer, aber Details sind nicht nötig.

Es kann aber passieren, dass die Standardserialisierung nicht erwünscht ist, wenn zum Beispiel beim Zurücklesen weitere Objekte erzeugt werden sollen oder wenn beim Schreiben eine bessere Abbildung durch Kompression möglich ist.

Für diesen Fall müssen spezielle (private!) Methoden implementiert werden. Beide müssen die nachstehenden Signaturen aufweisen:

private synchronized void writeObject( java.io.ObjectOutputStream s )
throws IOException

und

private synchronized void readObject( java.io.ObjectInputStream s )
throws IOException, ClassNotFoundException

Die Methode writeObject(ObjectOutputStream) ist für das Schreiben verantwortlich. Ist der Rumpf leer, gelangen keine Informationen in den Strom, und das Objekt wird folglich nicht gesichert. readObject(ObjectInputStream) wird während der Deserialisierung aufgerufen. Ist dieser Rumpf leer, werden keine Zustände rekonstruiert.

Mit diesen Methoden können wir also die Serialisierung selbst in die Hand nehmen und die Attribute so speichern, wie wir es für sinnvoll halten; eine Kompatibilität lässt sich erzwingen. Eine kleine Versionsnummer im Datenstrom könnte eine Verzweigung provozieren, in der die Daten der Version 1 oder andere Daten der Version 2 gelesen werden.

Beim Lesen können komplette Objekte wieder aufgebaut werden, und es lassen sich zum Beispiel nichttransiente Objekte wiederbeleben. Stellen wir uns einen Thread vor, dessen Zustände beim Schreiben persistent gemacht werden; beim Lesen wird ein Thread-Objekt wieder erzeugt und zum Leben erweckt.

Oberklassen serialisieren sich gleich mit

Wird eine Klasse serialisiert, so werden automatisch die Informationen der Oberklasse mit serialisiert. Hierbei gilt, dass wie beim Konstruktor erst die Attribute der Oberklasse in den Datenstrom geschrieben werden und anschließend die Attribute der Unterklasse. Insbesondere bedeutet dies, dass die Unterklasse nicht noch einmal die Attribute der Oberklasse speichern sollte. Das folgende Programm zeigt den Effekt:

Listing 7.34com/tutego/insel/io/ser/WriteTop.java

import java.io.*;

class Base implements Serializable {

private void writeObject( ObjectOutputStream oos ) {
System.err.println( "Base" );
}
}

public class WriteTop extends Base {
public static void main( String[] args ) throws IOException {
try ( ObjectOutputStream oos = new ObjectOutputStream( System.out ) ) {
oos.writeObject( new WriteTop() );
}
}

private void writeObject( ObjectOutputStream oos ) {
System.err.println( "Top" );
}
}

Doch noch den Standardserialisierer nutzen

Die privaten Methoden readObject(ObjectInputStream)/writeObject(ObjectOutputStream) arbeiten nach dem Alles-oder-nichts-Prinzip. Erkennt der Serialisierer, dass die Schnittstelle Serializable implementiert wird, fragt er die Klasse, ob sie die Methoden implementiert. Wenn nicht, beginnt bei der Serialisierung der Serialisierungsmechanismus eigenständig, die Attribute auszulesen und in den Datenstrom zu schreiben. Gibt es die privaten readObject(…)/writeObject(…)-Methoden, so wird der Serialisierer diese aufrufen und nicht selbst die Objekte nach den Werten fragen oder die Objekte mit Werten füllen.

Doch die Arbeit des Serialisierers ist eine große Hilfe. Falls viele Attribute zu speichern sind, fällt viel lästige Arbeit beim Programmieren an, da für jedes zu speichernde Attribut der Aufruf einer writeXXX(…)-Methode und beim Lesen eine entsprechende readXXX(…)-Methode nötig ist. Aus diesem Dilemma gibt es einen Ausweg, weil der Serialisierer in den privaten readObject(ObjectInputStream)/writeObject(ObjectOutputStream)-Methoden auch nachträglich dazu verpflichtet werden kann, die nichttransienten Attribute zu lesen oder zu schreiben. Und zwar helfen zwei Methoden aus den übergebenen Typen ObjectInputStream und ObjectOutputStream. Die Klasse ObjectOutputStream erweitert java.io.OutputStream unter anderem um die Methode defaultWriteObject(). Sie speichert die Attribute einer Klasse.

class java.io.ObjectOutputStream
extends OutputStream
implements ObjectOutput, ObjectStreamConstants
  • public final void defaultWriteObject() throws IOException
    Schreibt alle nichtstatischen und nichttransienten Attribute in den Datenstrom. Die Methode kann nur innerhalb einer privaten writeObject(ObjectOutputStream)-Methode aufgerufen werden; andernfalls erhalten wir eine NotActiveException.

Das Gleiche gilt für die Methode defaultReadObject() in der Klasse ObjectInputStream.

[»]Hinweis

Die Standarddeserialisierung hat mit finalen Variablen kein Problem. Wenn wir allerdings selbst readObject() aufrufen, können wir nicht problemlos finale Variablen initialisieren. Hier bietet sich an, auf defaultReadObject() zurückzugreifen oder abartig zu tricksen, was etwa nötig ist, wenn eine Variable final und transient ist, da ja transiente Variablen erst gar nicht von der Standardserialisierung berücksichtigt werden. Das Problem ist unter der Fehlernummer 6379948 (http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6379948) bekannt, und dort werden auch einige Lösungen präsentiert.

Beispiel für defaultReadObject()/defaultWriteObject() *

Unsere nächste Klasse SpecialWomen deklariert zwei Attribute: name und alter. Da manche Frauen nicht über ihr Alter sprechen wollen, soll alter nicht serialisiert werden; es ist transient. Wir implementieren eigene private readObject(…)/writeObject(…)-Methoden, die den Standardserialisierer bemühen. Bei der Rekonstruktion über readObject() wird die Frau dann immer 30 bleiben:

Listing 7.35com/tutego/insel/io/ser/SpecialWomen.java

package com.tutego.insel.io.ser;

import java.io.*;

public class SpecialWomen implements Serializable {

private static final long serialVersionUID = 2584203323009771108L;

String name = "Madonna";
transient int age = 30;

private void writeObject( ObjectOutputStream oos ) throws IOException {
oos.defaultWriteObject(); // Schreibe Name, aber kein Alter
}

private void readObject( ObjectInputStream ois ) throws IOException {
try {
ois.defaultReadObject(); // Lies Name, aber kein Alter
age = 30;
}
catch ( ClassNotFoundException e ) {
throw new IOException( "No class found. HELP!!" );
}
}
}

[»]Hinweis

Es ist gar nicht so abwegig, nur eine readObject(…)-, aber keine writeObject(…)-Methode zu implementieren. In readObject() lässt ein defaultReadObject() alle Eigenschaften initialisieren und danach noch Initialisierungsarbeit ähnlich einem Konstruktor durchführen. Dazu zählen etwa die Initialisierung von transienten Attributen, die Registrierung von Listenern und Weiteres.

 
Zum Seitenanfang

7.10.6Tiefe Objektkopien * Zur vorigen ÜberschriftZur nächsten Überschrift

Implementieren Klassen die Markierungsschnittstelle Serializable und überschreiben sie die clone()-Methode von Object, so können sie eine Kopie der Werte liefern. Die üblichen Implementierungen liefern aber nur flache Kopien. Dies bedeutet, dass Referenzen auf Objekte, die von dem zu klonenden Objekt ausgehen, beibehalten und diese Objekte nicht extra kopiert werden. Als Beispiel kann die Datenstruktur List genügen, die Map-Objekte enthält. Ein Klon dieser Liste ist lediglich eine zweite Liste, deren Elemente auf die gleichen Maps zeigen.

Möchten wir das Verhalten ändern und eine tiefe Kopie anfertigen, so haben wir dank eines kleinen Tricks damit keine Mühe: Wir könnten das zu klonende Objekt einfach serialisieren und dann wieder auspacken. Die zu klonenden Objekte müssen dann neben Cloneable noch das Serializable-Interface implementieren:

Listing 7.36com/tutego/insel/io/ser/Dolly.java, deepCopy()

@SuppressWarnings("unchecked")
public static <T> T deepCopy( T o ) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream( baos );
oos.writeObject( o );
oos.close();

ByteArrayInputStream bais = new ByteArrayInputStream( baos.toByteArray() );
return (T) new ObjectInputStream( bais ).readObject();
}

Das Einzige, was wir zum Gelingen der Methode deepCopy(…) beitragen müssen, ist, das Objekt in einem Byte-Feld zu serialisieren, es wieder auszulesen und zu einem Objekt zu konvertieren. Den Einsatz eines ByteArrayOutputStream haben wir schon beobachtet, als wir die Länge eines Objekts herausfinden wollten. Nun fügen wir das Feld einfach wieder zu einem ByteArrayInputStream hinzu, aus dessen Daten dann ObjectInputStream das Objekt rekreieren kann. Ein try mit Ressourcen können wir uns hier sparen, und alle Fehler werden an den Aufrufer von deepClone(…) weitergereicht.

Überzeugen wir uns anhand eines kleinen Programms, dass die tiefe Kopie tatsächlich etwas anderes als ein flaches clone() ist:

Listing 7.37Dolly.java, main()

Map<String,String> map = new HashMap<>();
map.put( "Cul de Paris",
"hinten unter dem Kleid getragenes Gestell oder Polster" );

LinkedList<Map<String,String>> o1 = new LinkedList<>();
o1.add( map );

@SuppressWarnings("unchecked")
List<Map<String, String>> o2 = (List<Map<String, String>>) o1.clone();

List<Map<String,String>> o3 = deepCopy( o1 );

map.clear();

System.out.println( o1 ); // [{}]
System.out.println( o2 ); // [{}]
System.out.println( o3 ); // [{Cul de Paris=hinten unter dem Kleid ...}]

Zunächst erstellen wir eine Map, die wir anschließend in eine Liste packen. Die Map enthält ein Pärchen. Kopiert clone() die Liste, so wird sie zwar selbst kopiert, aber nicht die referenzierten Map-Objekte – erst die tiefe Kopie kopiert die Map mit. Das sehen wir dann, wenn wir den Eintrag aus der Map löschen. Dann ergibt o1 genauso wie o2 eine leere Liste, da o2 nur die Verweise auf die Map gespeichert hat, die dann aber geleert ist. Anders ist dies bei o3, der tiefen Kopie: Hier ist das Paar noch vorhanden.

 
Zum Seitenanfang

7.10.7Versionenverwaltung und die SUID Zur vorigen ÜberschriftZur nächsten Überschrift

Die erste Version einer Klassenbibliothek ist in der Regel nicht vollständig und nicht beendet. Es kann gut sein, dass Attribute und Methoden nachträglich in die Klasse eingefügt, gelöscht oder modifiziert werden. Das bedeutet aber auch, dass die Serialisierung zu einem Problem werden kann. Denn ändert sich der Variablentyp oder kommen Variablen hinzu, ist eine gespeicherte Objektserialisierung nicht mehr gültig.

Bei der Serialisierung wird in Java nicht nur der Objektinhalt geschrieben, sondern zusätzlich eine eindeutige Kennung der Klasse, die UID. Die UID ist ein Hashcode aus Namen, Attributen, Parametern, Sichtbarkeit usw. Sie wird als long wie ein Attribut gespeichert. Ändert sich der Aufbau einer Klasse, ändern sich der Hashcode und damit die UID. Klassen mit unterschiedlicher UID sind nicht kompatibel. Erkennt der Lesemechanismus in einem Datenstrom eine UID, die nicht zur Klasse passt, wird eine InvalidClassException ausgelöst. Das bedeutet, dass schon ein einfaches Hinzufügen von Attributen zu einem Fehler führt.

Wir wollen uns dies einmal anhand einer einfachen Klasse ansehen. Wir entwickeln eine Klasse Player mit einem einfachen Ganzzahlattribut für ein Alter. Später ändern wir den Typ für das Alter in eine Fließkommazahl:

Listing 7.38com/tutego/insel/io/ser/Player.java, Player

class Player implements Serializable {
String name;
int age;
}

Dann benötigen wir noch das Hauptprogramm. Wir bilden ein Exemplar von Player und schreiben es in eine Datei:

Listing 7.39com/tutego/insel/io/ser/SerializePlayer.java, main()

try ( OutputStream fos = Files.newOutputStream( Paths.get( "player.ser" ) );
ObjectOutputStream oos = new ObjectOutputStream( fos ) ) {
oos.writeObject( new Player() );
}

Ohne Änderungen können wir es direkt wieder deserialisieren:

Listing 7.40com/tutego/insel/io/ser/DeSerializePlayer.java, main()

try ( InputStream fis = Files.newInputStream( Paths.get( "player.ser" ) );
ObjectInputStream ois = new ObjectInputStream( fis ) ) {
Player player = (Player) ois.readObject();
System.out.println( player );
}

Ändern wir die Klassendeklaration Player, sodass wir etwa aus dem int age ein double age machen, und führen dann noch einmal DeSerializePlayer auf, führt dies zu einem Fehler:

Exception in thread "main" java.io.InvalidClassException: com.tutego.insel.io.ser.Player; local class incompatible: stream classdesc serialVersionUID = 8962277452270582278, local class serialVersionUID = 44259824709362049
at java.io.ObjectStreamClass.initNonProxy(Unknown Source)
at java.io.ObjectInputStream.readNonProxyDesc(Unknown Source)
at java.io.ObjectInputStream.readClassDesc(Unknown Source)
at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
at java.io.ObjectInputStream.readObject0(Unknown Source)
at java.io.ObjectInputStream.readObject(Unknown Source)
at com.tutego.insel.io.ser.DeSerializePlayer.main(DeSerializePlayer.java:12)

Die eigene SUID

Dem oberen Fehlerauszug entnehmen wir, dass der Serialisierungsmechanismus die SUID selbst berechnet. Das Attribut ist als statische, finale Variable mit dem Namen serialVersionUID in der Klasse abgelegt. Ändern sich die Klassenattribute, ist es günstig, eine eigene SUID einzutragen, denn der Mechanismus zum Deserialisieren kann dann etwas gutmütiger mit den Daten umgehen. Beim Einlesen gibt es nämlich Informationen, die nicht hinderlich sind. Wir sprechen in diesem Zusammenhang auch von stream-kompatibel. Dazu gehören zwei Bereiche:

  • Neue Felder: Befinden sich in der neuen Klasse Attribute, die im Datenstrom nicht benannt sind, werden diese Attribute mit 0 oder null initialisiert.

  • Fehlende Felder: Befinden sich im Datenstrom Attribute, die in der neuen Klasse nicht vorkommen, werden sie einfach ignoriert.

Die SUID kann eigentlich beliebig sein, doch die IDE bzw. das kleine Java-Dienstprogramm serialver berechnet einen Wert, der der gleiche Wert wie der ist, den der Serialisierungsmechanismus berechnet. Auf diese Weise erreichen wir eine stream-kompatible Serialisierung.

[zB]Beispiel

Dies wollen wir für unsere Klasse Player mit dem Dienstprogramm testen:

$ serialver com.tutego.insel.io.ser.Player
com.tutego.insel.io.ser.Player: static final long serialVersionUID = ¿
8962277452270582278L;

Die Anweisung aus der letzten Zeile können wir in unsere Klasse Player kopieren. Wird danach ein weiteres Attribut in die Klasse gesetzt, gelöscht oder ändert sich der Typ eines Attributs, tritt die InvalidClassException nicht mehr auf, da die Stream-Kompatibilität über die serialVersionUID gewährleistet ist.

[+]Tipp

Da der Wert der Variablen serialVersionUID egal ist, kann sie bei 1 beginnen und immer dann, wenn es inkompatible Änderungen gibt, um eins erhöht werden.

 
Zum Seitenanfang

7.10.8Wie die ArrayList serialisiert * Zur vorigen ÜberschriftZur nächsten Überschrift

Am Beispiel einer java.util.ArrayList lässt sich sehr schön beobachten, wie sich die privaten Methoden writeObject(…) und readObject(…) nutzen lassen. Eine ArrayList beinhaltet eine Reihe von Elementen. Zur Speicherung nutzt die Datenstruktur ein internes Feld. Das Feld kann größer als die Anzahl der Elemente sein, damit bei jedem add(…) das Feld nicht immer neu vergrößert werden muss. Nehmen wir an, die ArrayList würde eine Standardserialisierung nutzen. Was passiert nun? Es könnte das Problem entstehen, dass bei nur einem Objektverweis in der Liste und einer internen Feldgröße von 1.000 Elementen leider 999 null-Verweise gespeichert würden. Das wäre aber Verschwendung! Besser ist es, eine angepasste Serialisierung zu verwenden:

Listing 7.41java.util.ArrayList.java, Ausschnitt

private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();

// Write out array length
s.writeInt(elementData.length);

// Write out all elements in the proper order.
for (int i=0; i<size; i++)
s.writeObject(elementData[i]);

if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}

}
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in size, and any hidden stuff
s.defaultReadObject();

// Read in array length and allocate array
int arrayLength = s.readInt();
Object[] a = elementData = new Object[arrayLength];

// Read in all elements in the proper order.
for (int i=0; i<size; i++)
a[i] = s.readObject();
}
 
Zum Seitenanfang

7.10.9Probleme mit der Serialisierung Zur vorigen ÜberschriftZur nächsten Überschrift

Der klassische Weg von einem Objekt zu einer persistenten Speicherung führt über den Serialisierungsmechanismus von Java über die Klassen ObjectOutputStream und ObjectInputStream. Die Serialisierung in Binärdaten ist aber nicht ohne Nachteile. Schwierig ist beispielsweise die Weiterverarbeitung von Nicht-Java-Programmen oder die nachträgliche Änderung ohne Einlesen und Wiederaufbauen der Objektverbünde. Wünschenswert ist daher eine Textrepräsentation. Diese hat nicht die oben genannten Nachteile.

Ein weiteres Problem ist die Skalierbarkeit. Die Standardserialisierung arbeitet nach dem Prinzip: Alles, was vom Basisknoten aus erreichbar ist, gelangt serialisiert in den Datenstrom. Ist der Objektgraph sehr groß, steigen die Zeit für die Serialisierung und das Datenvolumen an. Anders als bei anderen Persistenz-Konzepten ist es nicht möglich, nur die Änderungen (die Differenz) zu schreiben. Wenn sich zum Beispiel in einer sehr großen Adressliste die Hausnummer einer Person ändert, muss die gesamte Adressliste neu geschrieben werden – das nagt an der Performance.

Auch parallele Änderungen können ein Problem sein, da die Serialisierung über kein transaktionales Konzept verfügt. Während der Serialisierung sind die Objekte und Datenstrukturen nicht gesperrt, und ein anderer Thread kann derweil alles Mögliche modifizieren. Der Entwickler muss sich selbst auferlegen, während des Schreibens keine Änderungen vorzunehmen, damit der Schreibzugriff isoliert ist. Auch wenn es während des Schreibens ein Problem (etwa eine Ausnahme) gibt, kommt ein halbfertiger Datenstrom beim Client an.

Bibliotheksdesign

Heutzutage würden Bibliotheksdesigner keine Markierungsschnittstelle wie Serializable mehr einführen, sondern eine Annotation deklarieren. Die Serialisierungs-ID würde dann auch nicht mehr eine private statische Variable mit einem magischen Variablennamen sein, sondern ein Attribut der Annotation, sodass es zum Beispiel an einer Klasse heißen würde: @Serializable(1234566778L). Markierungsschnittstellen haben noch ein anderes Problem, das mit der Endgültigkeit von Vererbung und Typen zu tun hat: Implementiert eine Klasse einmal Serializable, so gilt diese Eigenschaft auch für alle Unterklassen, auch wenn vielleicht die Unterklassen gar nicht serialisierbar sein sollen. Auch hier lösen Annotationen das Problem, denn es lässt sich einstellen, ob Annotationen vererbt werden sollen oder nicht. Zu guter Letzt: Für transiente, also nicht serialisierte Zustände hätte kein Schlüsselwort in der Sprache reserviert, sondern lediglich eine neue Annotation deklariert werden müssen. Auch statt der magischen privaten Methoden readObject(…)/writeObject(…) hätte gut eine Annotation deklariert werden können, die eben die Methoden markiert, die bei der (De-)Serialisierung aufgerufen werden sollen.

 


Ihre Meinung

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.

<< zurück
 Zum Rheinwerk-Shop
Zum Rheinwerk-Shop: Java SE 8 Standard-Bibliothek Java SE 8 Standard-Bibliothek
Jetzt Buch bestellen

 Buchempfehlungen
Zum Rheinwerk-Shop: Java ist auch eine Insel
Java ist auch eine Insel


Zum Rheinwerk-Shop: Professionell entwickeln mit Java EE 8
Professionell entwickeln mit Java EE 8


Zum Rheinwerk-Shop: Besser coden
Besser coden


Zum Rheinwerk-Shop: Entwurfsmuster
Entwurfsmuster


Zum Rheinwerk-Shop: IT-Projektmanagement
IT-Projektmanagement


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und der Schweiz
InfoInfo

 
 


Copyright © Rheinwerk Verlag GmbH 2018
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.
Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

 
Nutzungsbestimmungen | Datenschutz | Impressum

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de

Cookie-Einstellungen ändern