CodeGym /Java-Blog /Random-DE /Serialisierung und Deserialisierung in Java
Autor
Volodymyr Portianko
Java Engineer at Playtika

Serialisierung und Deserialisierung in Java

Veröffentlicht in der Gruppe Random-DE
Hallo! In der heutigen Lektion sprechen wir über Serialisierung und Deserialisierung in Java. Wir beginnen mit einem einfachen Beispiel. Stellen Sie sich vor, Sie sind ein Computerspielentwickler. Wenn Sie in den 90er Jahren aufgewachsen sind und sich an die Spielekonsolen dieser Zeit erinnern, wissen Sie wahrscheinlich, dass ihnen etwas fehlte, das wir heute für selbstverständlich halten – die Möglichkeit, Spiele zu speichern und zu laden :) Wenn nicht, stellen Sie sich das vor!Serialisierung und Deserialisierung in Java - 1Ich fürchte, dass ein Spiel ohne diese Fähigkeiten heute zum Scheitern verurteilt wäre! Was bedeutet es überhaupt, ein Spiel zu „speichern“ und zu „laden“? Nun, wir verstehen die alltägliche Bedeutung: Wir wollen das Spiel dort fortsetzen, wo wir aufgehört haben. Dazu erstellen wir einen bestimmten „Checkpoint“, den wir später zum Laden des Spiels verwenden. Aber was bedeutet das für einen Programmierer und nicht für einen Gelegenheitsspieler? Die Antwort ist einfach: Wir speichern den Status unseres Programms. Nehmen wir an, Sie spielen in einem Strategiespiel gegen Spanien. Ihr Spiel hat einen Zustand: Welche Territorien hat jeder, wie viele Ressourcen hat jeder, welche Allianzen bestehen und mit wem, wer befindet sich im Krieg und so weiter. Diese Informationen, der Status unseres Programms, müssen irgendwie gespeichert werden, um die Daten wiederherzustellen und das Spiel fortzusetzen. Wie es passiert, Bei der Serialisierung in Java handelt es sich um den Prozess, bei dem der Zustand eines Objekts als Folge von Bytes gespeichert wird. Unter Deserialisierung versteht man in Java den Prozess der Wiederherstellung eines Objekts aus diesen Bytes. Jedes Java-Objekt kann in eine Bytesequenz konvertiert werden. Warum brauchen wir das? Wir haben wiederholt gesagt, dass Programme nicht für sich allein existieren. Am häufigsten interagieren sie miteinander, tauschen Daten aus usw. Ein Byte-Format ist hierfür praktisch und effizient. Beispielsweise können wir ein Objekt unseres SavedGame konvertierenKlasse in eine Folge von Bytes umwandeln, diese Bytes über das Netzwerk an einen anderen Computer übertragen und diese Bytes dann auf dem anderen Computer wieder in ein Java-Objekt umwandeln! Es klingt schwierig, oder? Es scheint, als wäre es schwierig, das alles umzusetzen: / Glücklicherweise ist das nicht der Fall! :) In Java ist die Serializable- Schnittstelle für den Serialisierungsprozess verantwortlich. Diese Schnittstelle ist äußerst einfach: Sie müssen keine einzige Methode implementieren, um sie zu verwenden! Schauen Sie sich an, wie einfach unsere Klasse zum Speichern von Spielen ist:

import java.io.Serializable;
import java.util.Arrays;

public class SavedGame implements Serializable {

   private static final long serialVersionUID = 1L;

   private String[] territoryInfo;
   private String[] resourceInfo;
   private String[] diplomacyInfo;

   public SavedGame(String[] territoryInfo, String[] resourceInfo, String[] diplomacyInfo){
       this.territoryInfo = territoryInfo;
       this.resourceInfo = resourceInfo;
       this.diplomacyInfo = diplomacyInfo;
   }

   public String[] getTerritoryInfo() {
       return territoryInfo;
   }

   public void setTerritoryInfo(String[] territoryInfo) {
       this.territoryInfo = territoryInfo;
   }

   public String[] getResourceInfo() {
       return resourceInfo;
   }

   public void setResourceInfo(String[] resourceInfo) {
       this.resourceInfo = resourceInfo;
   }

   public String[] getDiplomacyInfo() {
       return diplomacyInfo;
   }

   public void setDiplomacyInfo(String[] diplomacyInfo) {
       this.diplomacyInfo = diplomacyInfo;
   }

   @Override
   public String toString() {
       return "SavedGame{" +
               "territoryInfo=" + Arrays.toString(territoryInfo) +
               ", resourceInfo=" + Arrays.toString(resourceInfo) +
               ", diplomacyInfo=" + Arrays.toString(diplomacyInfo) +
               '}';
   }
}
Drei Arrays sind für Informationen über Gebiete, Ressourcen und Diplomatie verantwortlich, und die Serializable-Schnittstelle teilt der Java-Maschine mit: „ Alles ist in Ordnung, wenn Objekte dieser Klasse serialisiert werden können .“ Eine Schnittstelle ohne eine einzige Schnittstelle sieht seltsam aus :/ Warum ist das notwendig? Die Antwort auf diese Frage ist oben gegeben: Sie wird nur benötigt, um der Java-Maschine die notwendigen Informationen bereitzustellen. In einer früheren Lektion haben wir Markerschnittstellen kurz erwähnt. Hierbei handelt es sich um spezielle Informationsschnittstellen, die unsere Klassen einfach mit zusätzlichen Informationen markieren, die für die Java-Maschine in Zukunft nützlich sein werden. Sie verfügen über keine Methoden, die Sie implementieren müssen. Hier ist Serializable – eine solche Schnittstelle. Hier ist ein weiterer wichtiger Punkt: Warum brauchen wir dasprivate statische finale lange serialVersionUID- Variable, die wir in der Klasse definiert haben? Dieses Feld enthält die eindeutige Versionskennung der serialisierten Klasse. Jede Klasse, die die Serializable- Schnittstelle implementiert, verfügt über eine Versionskennung. Sie wird basierend auf dem Inhalt der Klasse bestimmt – Felder und ihre Deklarationsreihenfolge sowie Methoden und ihre Deklarationsreihenfolge. Und wenn wir einen Feldtyp und/oder die Anzahl der Felder in unserer Klasse ändern, ändert sich die Versionskennung sofort. Die serialVersionUID wird auch geschrieben, wenn die Klasse serialisiert wird. Wenn wir versuchen, ein Objekt zu deserialisieren, also aus einer Bytesequenz wiederherzustellen, wird der Wert von serialVersionUID mit dem Wert von serialVersionUID verglichender Klasse in unserem Programm. Wenn die Werte nicht übereinstimmen, wird eine java.io.InvalidClassException ausgelöst. Wir werden unten ein Beispiel dafür sehen. Um solche Situationen zu vermeiden, legen wir einfach die Versionskennung für unsere Klasse manuell fest. In unserem Fall ist es einfach gleich 1 (Sie können jede andere Zahl verwenden, die Sie möchten). Nun ist es an der Zeit, unser SavedGame- Objekt zu serialisieren und zu sehen, was passiert!

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class Main {

   public static void main(String[] args) throws IOException {

       // Create our object
       String[] territoryInfo = {"Spain has 6 provinces", "Russia has 10 provinces", "France has 8 provinces"};
       String[] resourceInfo = {"Spain has 100 gold", "Russia has 80 gold", "France has 90 gold"};
       String[] diplomacyInfo = {"France is at war with Russia, Spain has taken a neutral position"};

       SavedGame savedGame = new SavedGame(territoryInfo, resourceInfo, diplomacyInfo);

       // Create 2 streams to serialize the object and save it to a file
       FileOutputStream outputStream = new FileOutputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);

       // Save the game to a file
       objectOutputStream.writeObject(savedGame);

       // Close the stream and release resources
       objectOutputStream.close();
   }
}
Wie Sie sehen können, haben wir zwei Streams erstellt: FileOutputStream und ObjectOutputStream . Der erste weiß, wie man Daten in die Datei schreibt, und der zweite konvertiert Objekte in Bytes. Ähnliche verschachtelte Konstrukte, zum Beispiel new BufferedReader(new InputStreamReader(...)) , haben Sie bereits in früheren Lektionen gesehen, sie sollten Sie also nicht erschrecken :) Durch die Erstellung dieser Kette aus zwei Streams führen wir beide Aufgaben aus: Wir konvertieren das SavedGame- Objekt in eine Folge von Bytes und speichern es mit der Methode writeObject() in einer Datei . Und übrigens haben wir nicht einmal darauf geachtet, was wir bekommen haben! Es ist Zeit, sich die Datei anzusehen! *Hinweis: Es ist nicht erforderlich, die Datei im Voraus zu erstellen. Wenn eine Datei mit dem angegebenen Namen nicht existiert, wird sie automatisch erstellt . String;¬ТVзй{G xp t pФранция воюет СЃ Россией, Р˜СЃРїР°РЅРёСЏ заняла позицию нейтралит етаuq ~ t "РЈ Р˜СЃРїР°РЅРёРё 100 золотаt РЈ Р РѕСЃСЃРёРё 80 золотаt !РЈ Франции 90 Р·РѕР »РѕС‚Р°uq ~ t &РЈ Р˜СЃРїР°РЅРёРё 6 провинцийt %РЈ Р РѕСЃСЃРёРё 10 Рї ровинцийt &РЈ Франции 8 провинций Oh, oh :( Es scheint, dass unser Programm nicht funktioniert hat : ( Tatsächlich hat es funktioniert. Erinnern Sie sich, dass wir eine Folge von Bytes und nicht nur ein Objekt oder Text an die Datei gesendet haben? Nun, das ist diese Bytefolge sieht aus wie :) Es ist unser gespeichertes Spiel! Wenn wir unser ursprüngliches Objekt wiederherstellen wollen, also das Spiel dort starten und fortsetzen wollen, wo wir aufgehört haben, dann brauchen wir den umgekehrten Prozess: Deserialisierung. So sieht es für uns aus:

import java.io.*;

public class Main {

   public static void main(String[] args) throws IOException, ClassNotFoundException {

       FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);

       SavedGame savedGame = (SavedGame) objectInputStream.readObject();

       System.out.println(savedGame);
   }
}
Und hier ist das Ergebnis! SavedGame{territoryInfo=[Spanien hat 6 Provinzen, Russland hat 10 Provinzen, Frankreich hat 8 Provinzen], resourcesInfo=[Spanien hat 100 Gold, Russland hat 80 Gold, Frankreich hat 90 Gold], diplomacyInfo=[Frankreich befindet sich im Krieg mit Russland, Spanien hat eine neutrale Position eingenommen]} Ausgezeichnet! Wir haben es geschafft, den Status unseres Spiels zunächst in einer Datei zu speichern und ihn dann aus der Datei wiederherzustellen. Versuchen wir nun, dasselbe zu tun, entfernen jedoch die Versionskennung aus unserer SavedGame- Klasse. Wir werden nicht beide Klassen neu schreiben. Ihr Code wird derselbe sein. Wir entfernen einfach die private statische finale lange serialVersionUID aus der SavedGame- Klasse. Hier ist unser Objekt nach der Serialisierung: ¬н sr SavedGameі€MіuОm‰ [ diplomacyInfot [Ljava/lang/String;[ resourcesInfoq ~ [ territorialInfoq ~ xpur [Ljava.lang.String;¬ТVзй{G xp t pФранция воюет СЃ Р Р ѕСЃСЃРёРµР№, Р˜СЃРїР°РЅРёСЏ заняла позицию нейтралитетаuq ~ t "РЈ Р˜СЃРїР°РЅРёРё 100 золотР°t РЈ Р РѕСЃСЃРёРё 80 золотаt !РЈ Франции 90 золотаuq ~ t &РЈ Р ˜СЃРїР°РЅРёРё 6 провинцийt %РЈ Р РѕСЃСЃРёРё 10 провинцийt &РЈ Франции 8 РїСЂРѕРІРёРЅС †РёР№ Aber schauen Sie sich an, was passiert, wenn wir versuchen, es zu deserialisieren: InvalidClassException: lokale Klasse inkompatibel: stream classdesc serialVersionUID = -196410440475012755, lokale Klasse serialVersionUID = -6675950253085108747 Übrigens haben wir etwas Wichtiges verpasst. Offensichtlich lassen sich Strings und Grundelemente leicht serialisieren: Java verfügt sicherlich über einen eingebauten Mechanismus dafür. Was aber, wenn unsere serialisierbare Klasse Felder hat, die keine Grundelemente, sondern Verweise auf andere Objekte sind? Erstellen wir beispielsweise separate TerritoryInfo- , ResourceInfo- und DiplomacyInfo- Klassen, um mit unserer SavedGame- Klasse zu arbeiten.

public class TerritoryInfo {

   private String info;

   public TerritoryInfo(String info) {
       this.info = info;
   }

   public String getInfo() {
       return info;
   }

   public void setInfo(String info) {
       this.info = info;
   }

   @Override
   public String toString() {
       return "TerritoryInfo{" +
               "info='" + info + '\'' +
               '}';
   }
}

public class ResourceInfo {

   private String info;

   public ResourceInfo(String info) {
       this.info = info;
   }

   public String getInfo() {
       return info;
   }

   public void setInfo(String info) {
       this.info = info;
   }

   @Override
   public String toString() {
       return "ResourceInfo{" +
               "info='" + info + '\'' +
               '}';
   }
}

public class DiplomacyInfo {

   private String info;

   public DiplomacyInfo(String info) {
       this.info = info;
   }

   public String getInfo() {
       return info;
   }

   public void setInfo(String info) {
       this.info = info;
   }

   @Override
   public String toString() {
       return "DiplomacyInfo{" +
               "info='" + info + '\'' +
               '}';
   }
}
Und jetzt stehen wir vor einer Frage: Müssen alle diese Klassen serialisierbar sein, wenn wir unsere SavedGame- Klasse serialisieren möchten ?

import java.io.Serializable;
import java.util.Arrays;

public class SavedGame implements Serializable {

   private TerritoryInfo territoryInfo;
   private ResourceInfo resourceInfo;
   private DiplomacyInfo diplomacyInfo;

   public SavedGame(TerritoryInfo territoryInfo, ResourceInfo resourceInfo, DiplomacyInfo diplomacyInfo) {
       this.territoryInfo = territoryInfo;
       this.resourceInfo = resourceInfo;
       this.diplomacyInfo = diplomacyInfo;
   }

   public TerritoryInfo getTerritoryInfo() {
       return territoryInfo;
   }

   public void setTerritoryInfo(TerritoryInfo territoryInfo) {
       this.territoryInfo = territoryInfo;
   }

   public ResourceInfo getResourceInfo() {
       return resourceInfo;
   }

   public void setResourceInfo(ResourceInfo resourceInfo) {
       this.resourceInfo = resourceInfo;
   }

   public DiplomacyInfo getDiplomacyInfo() {
       return diplomacyInfo;
   }

   public void setDiplomacyInfo(DiplomacyInfo diplomacyInfo) {
       this.diplomacyInfo = diplomacyInfo;
   }

   @Override
   public String toString() {
       return "SavedGame{" +
               "territoryInfo=" + territoryInfo +
               ", resourceInfo=" + resourceInfo +
               ", diplomacyInfo=" + diplomacyInfo +
               '}';
   }
}
Alles klar dann! Lass es uns testen! Wir lassen zunächst alles so wie es ist und versuchen ein SavedGame- Objekt zu serialisieren:

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class Main {

   public static void main(String[] args) throws IOException {

       // Create our object
       TerritoryInfo territoryInfo = new TerritoryInfo("Spain has 6 provinces, Russia has 10 provinces, France has 8 provinces");
       ResourceInfo resourceInfo = new ResourceInfo("Spain has 100 gold, Russia has 80 gold, France has 90 gold");
       DiplomacyInfo diplomacyInfo =  new DiplomacyInfo("France is at war with Russia, Spain has taken a neutral position");


       SavedGame savedGame = new SavedGame(territoryInfo, resourceInfo, diplomacyInfo);

       FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);

       objectOutputStream.writeObject(savedGame);

       objectOutputStream.close();
   }
}
Ergebnis: Ausnahme im Thread „main“ java.io.NotSerializableException: DiplomacyInfo Es hat nicht funktioniert! Hier ist also die Antwort auf unsere Frage. Wenn ein Objekt serialisiert wird, werden alle Objekte, auf die seine Instanzvariablen verweisen, serialisiert. Und wenn diese Objekte auch auf andere Objekte verweisen, werden sie ebenfalls serialisiert. Und immer so weiter. Alle Klassen in dieser Kette müssen serialisierbar sein , andernfalls können sie nicht serialisiert werden und es wird eine Ausnahme ausgelöst. Dies kann übrigens später zu Problemen führen. Was sollen wir beispielsweise tun, wenn wir während der Serialisierung keinen Teil einer Klasse benötigen? Oder was wäre, wenn wir unsere TerritoryInfo- Klasse „durch Vererbung“ als Teil einer Bibliothek erhalten würden? Und nehmen wir weiter an, dass es so ist.und dementsprechend können wir es nicht ändern. Das würde bedeuten, dass wir unserer SavedGame- Klasse kein TerritoryInfo- Feld hinzufügen können , da dann die gesamte SavedGame- Klasse nicht mehrialisierbar wäre! Das ist ein Problem: / In Java wird ein solches Problem durch das Schlüsselwort transient gelöst . Wenn Sie dieses Schlüsselwort zu einem Feld Ihrer Klasse hinzufügen, wird dieses Feld nicht serialisiert. Versuchen wir, eines der Felder unserer SavedGame- Klasse transient zu machen , und dann werden wir ein einzelnes Objekt serialisieren und wiederherstellen. Serialisierung und Deserialisierung in Java - 2

import java.io.Serializable;

public class SavedGame implements Serializable {

   private transient TerritoryInfo territoryInfo;
   private ResourceInfo resourceInfo;
   private DiplomacyInfo diplomacyInfo;

   public SavedGame(TerritoryInfo territoryInfo, ResourceInfo resourceInfo, DiplomacyInfo diplomacyInfo) {
       this.territoryInfo = territoryInfo;
       this.resourceInfo = resourceInfo;
       this.diplomacyInfo = diplomacyInfo;
   }

   // ...getters, setters, toString()...
}



import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class Main {

   public static void main(String[] args) throws IOException {

       // Create our object
       TerritoryInfo territoryInfo = new TerritoryInfo("Spain has 6 provinces, Russia has 10 provinces, France has 8 provinces");
       ResourceInfo resourceInfo = new ResourceInfo("Spain has 100 gold, Russia has 80 gold, France has 90 gold");
       DiplomacyInfo diplomacyInfo =  new DiplomacyInfo("France is at war with Russia, Spain has taken a neutral position");


       SavedGame savedGame = new SavedGame(territoryInfo, resourceInfo, diplomacyInfo);

       FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);

       objectOutputStream.writeObject(savedGame);

       objectOutputStream.close();
   }
}


import java.io.*;

public class Main {

   public static void main(String[] args) throws IOException, ClassNotFoundException {

       FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);

       SavedGame savedGame = (SavedGame) objectInputStream.readObject();

       System.out.println(savedGame);

       objectInputStream.close();


   }
}
Und hier ist das Ergebnis: SavedGame{territoryInfo=null, resourcesInfo=ResourceInfo{info='Spanien hat 100 Gold, Russland hat 80 Gold, Frankreich hat 90 Gold'}, diplomacyInfo=DiplomacyInfo{info='Frankreich befindet sich im Krieg mit Russland, Spanien hat eine neutrale Position eingenommen'}} Damit haben wir eine Antwort auf die Frage erhalten, welcher Wert einem transienten Feld zugewiesen wird. Ihm wird der Standardwert zugewiesen. Für Objekte ist dies null . Sie können ein ausgezeichnetes Kapitel zu diesem Thema im Buch „Head-First Java“ lesen. Achten Sie darauf :)
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION