Die Serialisierung ist ein Prozess, der ein Objekt in eine Folge von Bytes übersetzt, aus denen es dann vollständig wiederhergestellt werden kann. Warum wird das benötigt?
Tatsache ist, dass während der normalen Programmausführung die maximale Lebensdauer eines Objekts bekannt ist - vom Start des Programms bis zu seinem Ende. Durch die Serialisierung können Sie dieses Framework erweitern und das Objekt zwischen den Programmstarts auf dieselbe Weise „beleben“.
Ein zusätzlicher Bonus für alles ist die Erhaltung der plattformübergreifenden Nutzung. Unabhängig davon, welches Betriebssystem Sie haben, übersetzt die Serialisierung das Objekt in einen Bytestrom, der auf jedem Betriebssystem wiederhergestellt werden kann. Wenn Sie ein Objekt über das Netzwerk übertragen müssen, können Sie das Objekt serialisieren, in einer Datei speichern und über das Netzwerk an den Empfänger übertragen. Er kann das empfangene Objekt wiederherstellen. Durch die Serialisierung können Sie auch Methoden (Java RMI) aus der Ferne aufrufen, die sich auf verschiedenen Computern mit möglicherweise unterschiedlichen Betriebssystemen befinden, und mit ihnen arbeiten, als wären sie auf dem Computer des aufrufenden Java-Prozesses.
Die Implementierung eines Serialisierungsmechanismus ist recht einfach. Ihre Klasse muss die
serialisierbare Schnittstelle implementieren. Diese Schnittstelle ist ein Bezeichner ohne Methoden, der jvm jedoch mitteilt, dass Objekte dieser Klasse serialisiert werden können. Da der Serialisierungsmechanismus mit dem grundlegenden Eingabe- / Ausgabesystem verbunden ist und das Objekt in einen
Bytestream übersetzt , müssen Sie zum Ausführen einen Ausgabestream
OutputStream erstellen , ihn in
ObjectOutputStream packen und die Methode
writeObject () aufrufen. Um ein Objekt wiederherzustellen, müssen Sie einen
InputStream in einen
ObjectInputStream packen und die Methode
readObject () aufrufen.Während der Serialisierung wird zusammen mit dem serialisierbaren Objekt dessen Objektdiagramm gespeichert. Das heißt, Alle damit verbundenen Objekte, Objekte anderer Klassen, werden ebenfalls damit serialisiert.
Betrachten Sie ein Beispiel für die Serialisierung eines Objekts der Klasse Person.
import java.io.*; class Home implements Serializable { private String home; public Home(String home) { this.home = home; } public String getHome() { return home; } } public class Person implements Serializable { private String name; private int countOfNiva; private String fatherName; private Home home; public Person(String name, int countOfNiva, String fatherName, Home home) { this.name = name; this.countOfNiva = countOfNiva; this.fatherName = fatherName; this.home = home; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", countOfNiva=" + countOfNiva + ", fatherName='" + fatherName + '\'' + ", home=" + home + '}'; } public static void main(String[] args) throws IOException, ClassNotFoundException { Home home = new Home("Vishnevaia 1"); Person igor = new Person("Igor", 2, "Raphael", home); Person renat = new Person("Renat", 2, "Raphael", home);
Fazit: Before Serialize: Person{name='Igor', countOfNiva=2, fatherName='Raphael', home=Home@355da254} Person{name='Renat', countOfNiva=2, fatherName='Raphael', home=Home@355da254} After Restored From Byte: Person{name='Igor', countOfNiva=2, fatherName='Raphael', home=Home@27973e9b} Person{name='Renat', countOfNiva=2, fatherName='Raphael', home=Home@27973e9b} After Restored: Person{name='Igor', countOfNiva=2, fatherName='Raphael', home=Home@312b1dae} Person{name='Renat', countOfNiva=2, fatherName='Raphael', home=Home@312b1dae}
In diesem Beispiel wird die
Home- Klasse erstellt, um zu demonstrieren, dass beim Serialisieren eines
Personenobjekts das Diagramm seiner Objekte damit serialisiert wird. Die
Home- Klasse muss auch die
Serializable- Schnittstelle implementieren, andernfalls tritt eine
java.io.NotSerializableException auf. Das Beispiel beschreibt auch die Serialisierung mit der
ByteArrayOutputStream- Klasse.
Eine interessante Schlussfolgerung kann aus den Ergebnissen der Programmausführung gezogen werden:
Wenn Objekte wiederhergestellt werden, die vor der Serialisierung auf dasselbe Objekt verweisen, wird dieses Objekt nur einmal wiederhergestellt . Dies ist nach der Wiederherstellung auf denselben Links in den Objekten zu sehen:
After Restored From Byte: Person{name='Igor', countOfNiva=2, fatherName='Raphael', home=Home@27973e9b} Person{name='Renat', countOfNiva=2, fatherName='Raphael', home=Home@27973e9b} After Restored: Person{name='Igor', countOfNiva=2, fatherName='Raphael', home=Home@312b1dae} Person{name='Renat', countOfNiva=2, fatherName='Raphael', home=Home@312b1dae}
Es ist jedoch auch ersichtlich, dass beim Aufzeichnen mit zwei Ausgabestreams (wir haben
ObjectInputStream und
ByteArrayOutputStream ) das
Home- Objekt neu erstellt wird, obwohl es bereits zuvor in einem der Streams erstellt wurde. Wir sehen dies an verschiedenen Adressen von Heimobjekten, die in zwei Strömen empfangen wurden. Es stellt sich heraus, dass wir, wenn Sie mit einem Ausgabestream serialisieren und dann das Objekt wiederherstellen, die Garantie haben, das gesamte Netzwerk von Objekten ohne unnötige Duplikate wiederherzustellen. Natürlich kann sich während der Ausführung des Programms der Zustand der Objekte ändern, aber dies liegt im Gewissen des Programmierers.
Das ProblemDas Beispiel zeigt auch, dass beim Wiederherstellen eines Objekts eine
ClassNotFoundException auftreten kann. Was ist der Grund dafür? Tatsache ist, dass wir ein Objekt der
Person- Klasse einfach in eine Datei serialisieren und über das Netzwerk an unseren Freund übertragen können, der das Objekt durch eine andere Anwendung wiederherstellen kann, in der die
Person- Klasse einfach nicht vorhanden ist.
Seine Serialisierung. Wie macht man?Was ist, wenn Sie die Serialisierung selbst verwalten möchten? In Ihrem Objekt werden beispielsweise der Benutzername und das Kennwort der Benutzer gespeichert. Sie müssen es für die weitere Übertragung über das Netzwerk serialisieren. Die Übergabe eines Passworts ist in diesem Fall äußerst unzuverlässig. Wie kann man dieses Problem lösen? Es gibt zwei Möglichkeiten. Verwenden Sie zunächst das Schlüsselwort
transient . Zweitens, anstatt
Serializable Interest zu realisieren, verwenden Sie die Erweiterung - die
Externalizable- Schnittstelle. Betrachten Sie die Beispiele der ersten und zweiten Methode, um sie zu vergleichen.
Der erste Weg - Serialisierung mit Transient import java.io.*; public class Logon implements Serializable { private String login; private transient String password; public Logon(String login, String password) { this.login = login; this.password = password; } @Override public String toString() { return "Logon{" + "login='" + login + '\'' + ", password='" + password + '\'' + '}'; } public static void main(String[] args) throws IOException, ClassNotFoundException { Logon igor = new Logon("IgorIvanovich", "Khoziain"); Logon renat = new Logon("Renat", "2500RUB"); System.out.println("Before: \n" + igor); System.out.println(renat); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("Externals.out")); out.writeObject(igor); out.writeObject(renat); out.close(); ObjectInputStream in = new ObjectInputStream(new FileInputStream("Externals.out")); igor = (Logon) in.readObject(); renat = (Logon) in.readObject(); System.out.println("After: \n" + igor); System.out.println(renat); } }
Fazit: Before: Logon{login='IgorIvanovich', password='Khoziain'} Logon{login='Renat', password='2500RUB'} After: Logon{login='IgorIvanovich', password='null'} Logon{login='Renat', password='null'}
Der zweite Weg - Serialisierung mit der Implementierung der Externalizable-Schnittstelle import java.io.*; public class Logon implements Externalizable { private String login; private String password; public Logon() { } public Logon(String login, String password) { this.login = login; this.password = password; } @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(login); } @Override public String toString() { return "Logon{" + "login='" + login + '\'' + ", password='" + password + '\'' + '}'; } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { login = (String) in.readObject(); } public static void main(String[] args) throws IOException, ClassNotFoundException { Logon igor = new Logon("IgorIvanovich", "Khoziain"); Logon renat = new Logon("Renat", "2500RUB"); System.out.println("Before: \n" + igor); System.out.println(renat); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("Externals.out")); out.writeObject(igor); out.writeObject(renat); out.close(); ObjectInputStream in = new ObjectInputStream(new FileInputStream("Externals.out")); igor = (Logon) in.readObject(); renat = (Logon) in.readObject(); System.out.println("After: \n" + igor); System.out.println(renat); } }
Fazit: Before: Logon{login='IgorIvanovich', password='Khoziain'} Logon{login='Renat', password='2500RUB'} After: Logon{login='IgorIvanovich', password='null'} Logon{login='Renat', password='null'}
Der erste Unterschied zwischen den beiden Optionen, der auffällt, ist die Größe des Codes. Bei der Implementierung der
Externalizable- Schnittstelle müssen zwei Methoden überschrieben werden:
writeExternal () und
readExternal () . In der
writeExternal () -Methode geben wir an, welche Felder serialisiert werden und wie, in
readExternal (), wie sie gelesen werden sollen. Bei Verwendung des Wortes
transient geben wir explizit an, welches Feld oder welche Felder nicht serialisiert werden müssen. Wir stellen außerdem fest, dass wir in der zweiten Methode explizit einen Standardkonstruktor erstellt haben, außerdem einen öffentlichen. Warum wird das gemacht? Versuchen wir, den Code ohne diesen Konstruktor auszuführen. Und schauen Sie sich die Ausgabe an:
Before: Logon{login='IgorIvanovich', password='Khoziain'} Logon{login='Renat', password='2500RUB'} Exception in thread "main" java.io.InvalidClassException: Logon; no valid constructor at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:169) at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:874) at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2043) at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573) at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431) at Logon.main(Logon.java:45)
Wir haben
die Ausnahme
java.io.InvalidClassException . Was ist der Grund dafür? Wenn Sie den Stack-Trace durchlaufen, können Sie feststellen, dass der Konstruktor der
ObjectStreamClass- Klasse Zeilen enthält:
if (externalizable) { cons = getExternalizableConstructor(cl); } else { cons = getSerializableConstructor(cl);
Für die
Externalizable- Schnittstelle wird die Konstruktormethode
getExternalizableConstructor () aufgerufen
, innerhalb derer wir durch
Reflection versuchen, den Standardkonstruktor der Klasse
abzurufen, für die wir das Objekt wiederherstellen. Wenn wir es nicht finden können oder es nicht
öffentlich ist , erhalten wir eine Ausnahme. Sie können diese Situation wie folgt umgehen: Erstellen Sie keinen expliziten Konstruktor in der Klasse und füllen Sie die Felder mit Setzern und erhalten Sie den Wert mit Getter. Beim Kompilieren der Klasse wird dann ein Standardkonstruktor erstellt, der für
getExternalizableConstructor () verfügbar ist. Für
Serializable ruft die Methode
getSerializableConstructor () den Konstruktor der
Object- Klasse ab und sucht die gewünschte Klasse daraus. Wenn sie nicht gefunden wird, wird eine
ClassNotFoundException- Ausnahme
angezeigt . Es stellt sich heraus, dass der Hauptunterschied zwischen
Serializable und
Externalizable darin besteht, dass erstere keinen Konstruktor zum Erstellen der Objektwiederherstellung benötigen. Es wird einfach vollständig von Bytes wiederhergestellt. Zum zweiten wird während der Wiederherstellung zuerst ein Objekt mit dem Konstruktor am Deklarationspunkt erstellt, und dann werden Werte seiner Felder aus Bytes, die während der Serialisierung empfangen wurden, in das Objekt geschrieben. Persönlich bevorzuge ich die erste Methode, sie ist viel einfacher. Selbst wenn wir das Serialisierungsverhalten noch festlegen müssen, können wir
Externalizable nicht verwenden und
Serializable implementieren, indem wir die Methoden
writeObject () und
readObject () hinzufügen (ohne sie zu überschreiben). Damit sie jedoch „arbeiten“ können, muss ihre Unterschrift unbedingt beachtet werden.
import java.io.*; public class Talda implements Serializable { private String name; private String description; public Talda(String name, String description) { this.name = name; this.description = description; } private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); System.out.println("Our writeObject"); } private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); System.out.println("Our readObject"); } @Override public String toString() { return "Talda{" + "name='" + name + '\'' + ", description='" + description + '\'' + '}'; } public static void main(String[] args) throws IOException, ClassNotFoundException { Talda partizanka = new Talda("Partizanka", "Viiiski"); System.out.println("Before: \n" + partizanka); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("Talda.out")); out.writeObject(partizanka); out.close(); ObjectInputStream in = new ObjectInputStream(new FileInputStream("Talda.out")); partizanka = (Talda) in.readObject(); System.out.println("After: \n" + partizanka); } }
Fazit: Before: Talda{name='Partizanka', description='Viiiski'} Our writeObject Our readObject After: Talda{name='Partizanka', description='Viiiski'}
In unseren hinzugefügten Methoden werden
defaultWriteObject () und
defaultReadObject () aufgerufen. Sie sind für die Standardserialisierung verantwortlich, als ob sie ohne die von uns hinzugefügten Methoden funktioniert hätte.
Tatsächlich ist dies nur die Spitze des Eisbergs. Wenn Sie sich weiter mit dem Mechanismus der Serialisierung befassen, können Sie mit hoher Wahrscheinlichkeit mehr Nuancen finden, die wir sagen: "Serialisierung ... ist nicht so einfach."