CodeGym /Java Blog /Willekeurig /Wat is het verschil tussen serialisatie en deserialisatie...
John Squirrels
Niveau 41
San Francisco

Wat is het verschil tussen serialisatie en deserialisatie in Java?

Gepubliceerd in de groep Willekeurig
Hoi! In de les van vandaag hebben we het over serialisatie en deserialisatie in Java. We beginnen met een eenvoudig voorbeeld. Laten we zeggen dat je een computerspel hebt gemaakt. Als je bent opgegroeid in de jaren 90 en je de gameconsoles uit die tijd herinnert, weet je waarschijnlijk dat ze iets misten dat we tegenwoordig als vanzelfsprekend beschouwen: de mogelijkheid om games op te slaan en te laden :) Zo niet, stel je dat eens voor! Wat is het verschil tussen serialisatie en deserialisatie in Java?  - 1 Ik ben bang dat een game zonder deze vaardigheden vandaag gedoemd zou zijn! Wat betekent het eigenlijk om een ​​spel te "opslaan" en "laden"? Nou, we begrijpen de gewone betekenis: we willen het spel voortzetten vanaf de plek waar we waren gebleven. Om dit te doen, creëren we een soort "controlepunt", dat we vervolgens gebruiken om het spel te laden. Maar wat betekent dit voor een programmeur in plaats van een casual gamer? Het antwoord is simpel: wij'. Laten we zeggen dat je als Spanje speelt in Strategium. Je spel heeft een staat: wie bezit welke gebieden, wie heeft hoeveel grondstoffen, wie heeft een alliantie met wie, wie voert oorlog met wie, enzovoort. We moeten deze informatie, de status van ons programma, op de een of andere manier opslaan om deze in de toekomst te herstellen en het spel voort te zetten. Want dit is precies waar serialisatie en deserealisatie voor zijn. Serialisatie is het proces waarbij de status van een object wordt opgeslagen in een reeks bytes. Deserialisatieis het proces van het herstellen van een object uit deze bytes. Elk Java-object kan worden geconverteerd naar een bytereeks. Waarom zouden we dat nodig hebben? We hebben meer dan eens gezegd dat programma's niet op zichzelf staan. Meestal communiceren ze met andere programma's, wisselen ze gegevens uit, enz. En een bytereeks is een handig en efficiënt formaat. We kunnen ons object bijvoorbeeld omzetten SavedGamein een reeks bytes, deze bytes via het netwerk naar een andere computer sturen en deze bytes op de tweede computer weer omzetten in een Java-object! Klinkt moeilijk, toch? En het implementeren van dit proces lijkt lastig :/ Gelukkig is dit niet zo! :) Op Java is deSerializableinterface is verantwoordelijk voor het serialisatieproces. Deze interface is uiterst eenvoudig: u hoeft geen enkele methode te implementeren om hem te gebruiken! Zo eenvoudig ziet onze spelbesparende les eruit:

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

public class SavedGame implements Serializable {

   private static final long serialVersionUID = 1L;

   private String[] territoriesInfo;
   private String[] resourcesInfo;
   private String[] diplomacyInfo;

   public SavedGame(String[] territoriesInfo, String[] resourcesInfo, String[] diplomacyInfo){
       this.territoriesInfo = territoriesInfo;
       this.resourcesInfo = resourcesInfo;
       this.diplomacyInfo = diplomacyInfo;
   }

   public String[] getTerritoriesInfo() {
       return territoriesInfo;
   }

   public void setTerritoriesInfo(String[] territoriesInfo) {
       this.territoriesInfo = territoriesInfo;
   }

   public String[] getResourcesInfo() {
       return resourcesInfo;
   }

   public void setResourcesInfo(String[] resourcesInfo) {
       this.resourcesInfo = resourcesInfo;
   }

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

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

   @Override
   public String toString() {
       return "SavedGame{" +
               "territoriesInfo=" + Arrays.toString(territoriesInfo) +
               ", resourcesInfo=" + Arrays.toString(resourcesInfo) +
               ", diplomacyInfo=" + Arrays.toString(diplomacyInfo) +
               '}';
   }
}
De drie arrays zijn verantwoordelijk voor informatie over territoria, middelen en diplomatie. De Serializable-interface vertelt de virtuele Java-machine: " Alles is in orde - indien nodig kunnen objecten van deze klasse worden geserialiseerd ". Een interface zonder een enkele interface ziet er raar uit :/ Waarom is het nodig? Het antwoord op deze vraag is hierboven te zien: het dient alleen om de noodzakelijke informatie aan de Java virtual machine te verstrekken. In een van onze vorige lessen hebben we het kort over marker-interfaces gehad . Dit zijn speciale informatieve interfaces die onze klassen eenvoudig markeren met aanvullende informatie die in de toekomst nuttig zal zijn voor de Java-machine. Ze hebben geen methoden die u moet implementeren.Serializableis een van die interfaces. Nog een belangrijk punt: waarom hebben we de private static final long serialVersionUIDvariabele nodig die we in de klas hebben gedefinieerd? Waarom is het nodig? Dit veld bevat een unieke identificatie voor de versie van de geserialiseerde klasse . Elke klasse die de Serializableinterface implementeert, heeft een versionidentifier. Het wordt berekend op basis van de inhoud van de klasse: de velden, de volgorde waarin ze zijn gedeclareerd, methoden, enz. Als we het type veld en/of het aantal velden in onze klasse wijzigen, verandert de versie-ID onmiddellijk . serialVersionUIDwordt ook geschreven wanneer de klasse is geserialiseerd. Wanneer we proberen een object te deserialiseren, dat wil zeggen een object uit een reeks bytes herstellen, serialVersionUIDwordt het bijbehorende object vergeleken met de waarde vanserialVersionUIDvoor de klas in ons programma. Als de waarden niet overeenkomen, wordt een java.io. InvalidClassException wordt gegenereerd. Een voorbeeld daarvan zien we hieronder. Om dit te voorkomen, stellen we de versie-ID eenvoudig handmatig in onze klasse in. In ons geval is het gewoon gelijk aan 1 (maar u kunt elk ander getal vervangen dat u wilt). Welnu, het is tijd om te proberen ons object te serialiseren SavedGameen te kijken wat er gebeurt!

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[] resourcesInfo = {"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, resourcesInfo, 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 free resources
       objectOutputStream.close();
   }
}
Zoals je kunt zien, hebben we 2 streams gemaakt: FileOutputStreamen ObjectOutputStream. De eerste kan gegevens naar een bestand schrijven en de tweede converteert objecten naar bytes. Je hebt al vergelijkbare "geneste" constructies gezien, bijvoorbeeld , new BufferedReader(new InputStreamReader(...))in eerdere lessen, dus die zouden je niet moeten afschrikken :) Door zo'n "ketting" van twee stromen te maken, voeren we beide taken uit: we zetten het SavedGameobject om in een set van bytes en sla het op in een bestand met behulp van de writeObject()methode. En trouwens, we hebben niet eens gekeken naar wat we kregen! Het is tijd om naar het dossier te kijken! *Let op: u hoeft het bestand niet vooraf aan te maken. Als er geen bestand met die naam bestaat, wordt het automatisch aangemaakt* En hier is de inhoud!

¬н sr SavedGame [ diplomacyInfot [Ljava/lang/String;[ resourcesInfoq ~ [ territoriesInfoq ~ xpur [Ljava.lang.String;­ТVзй{G xp t pФранция воюет СЃ Россией, Испания заняла позицию нейтралитетаuq ~ t "РЈ Испании 100 золотаt РЈ Р РѕСЃСЃРёРё 80 золотаt !РЈ Франции 90 золотаuq ~ t &РЈ Испании 6 провинцийt %РЈ Р РѕСЃСЃРёРё 10 провинцийt &РЈ Франции 8 провинций
Uh-oh :( Het lijkt erop dat ons programma niet werkte :( Het werkte zelfs. Herinner je je dat we een set bytes stuurden, niet alleen een object of tekst, naar het bestand? Nou, dit is wat dat set van bytes ziet eruit als :) Dit is ons opgeslagen spel! Als we ons oorspronkelijke object willen herstellen, dwz het spel starten en voortzetten waar we gebleven waren, dan hebben we het omgekeerde proces nodig: deserialisatie. Zo zal het eruit zien in onze geval:

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);
   }
}
En hier is het resultaat!

SavedGame{territoriesInfo=["Spain has 6 provinces, Russia has 10 provinces, France has 8 provinces], resourcesInfo=[Spain has 100 gold, Russia has 80 gold, France has 90 gold], diplomacyInfo=[France is at war with Russia, Spain has taken a neutral position]}
Uitstekend! We zijn erin geslaagd om eerst de status van onze game in een bestand op te slaan en deze vervolgens vanuit het bestand te herstellen. Laten we nu proberen hetzelfde te doen, maar zonder de versie-ID voor onze SavedGameklasse. We gaan onze beide lessen niet herschrijven. Hun code blijft hetzelfde, maar we verwijderen ze private static final long serialVersionUIDuit de SavedGameklas. Dit is ons object na serialisatie:

¬н sr SavedGameі€MіuОm‰ [ diplomacyInfot [Ljava/lang/String;[ resourcesInfoq ~ [ territoriesInfoq ~ xpur [Ljava.lang.String;­ТVзй{G xp t pФранция воюет СЃ Россией, Испания заняла позицию нейтралитетаuq ~ t "РЈ Испании 100 золотаt РЈ Р РѕСЃСЃРёРё 80 золотаt !РЈ Франции 90 золотаuq ~ t &РЈ Испании 6 провинцийt %РЈ Р РѕСЃСЃРёРё 10 провинцийt &РЈ Франции 8 провинций
Maar kijk eens wat er gebeurt als we het proberen te deserialiseren:

InvalidClassException: local class incompatible: stream classdesc serialVersionUID = -196410440475012755, local class serialVersionUID = -6675950253085108747
Dit is de uitzondering die we hierboven noemden. Trouwens, we hebben iets belangrijks gemist. Het is logisch dat strings en primitieven gemakkelijk kunnen worden geserialiseerd: Java heeft waarschijnlijk een of ander ingebouwd mechanisme om dit te doen. Maar wat als onze serializableklasse velden heeft die geen primitieven zijn, maar verwijzingen naar andere objecten? Laten we bijvoorbeeld afzonderlijke TerritoriesInfo, ResourcesInfoen DiplomacyInfoklassen maken om met onze klas te werken SavedGame.

public class TerritoriesInfo {
  
   private String info;

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

   public String getInfo() {
       return info;
   }

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

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

public class ResourcesInfo {

   private String info;

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

   public String getInfo() {
       return info;
   }

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

   @Override
   public String toString() {
       return "ResourcesInfo{" +
               "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 + '\'' +
               '}';
   }
}
En nu rijst een vraag: moeten al deze klassen zijn als we onze gewijzigde klasse Serializablewillen serialiseren ?SavedGame

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

public class SavedGame implements Serializable {

   private TerritoriesInfo territoriesInfo;
   private ResourcesInfo resourcesInfo;
   private DiplomacyInfo diplomacyInfo;

   public SavedGame(TerritoriesInfo territoriesInfo, ResourcesInfo resourcesInfo, DiplomacyInfo diplomacyInfo) {
       this.territoriesInfo = territoriesInfo;
       this.resourcesInfo = resourcesInfo;
       this.diplomacyInfo = diplomacyInfo;
   }

   public TerritoriesInfo getTerritoriesInfo() {
       return territoriesInfo;
   }

   public void setTerritoriesInfo(TerritoriesInfo territoriesInfo) {
       this.territoriesInfo = territoriesInfo;
   }

   public ResourcesInfo getResourcesInfo() {
       return resourcesInfo;
   }

   public void setResourcesInfo(ResourcesInfo resourcesInfo) {
       this.resourcesInfo = resourcesInfo;
   }

   public DiplomacyInfo getDiplomacyInfo() {
       return diplomacyInfo;
   }

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

   @Override
   public String toString() {
       return "SavedGame{" +
               "territoriesInfo=" + territoriesInfo +
               ", resourcesInfo=" + resourcesInfo +
               ", diplomacyInfo=" + diplomacyInfo +
               '}';
   }
}
Nou, laten we het testen! Laten we alles laten zoals het is en proberen een SavedGameobject te serialiseren:

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(territoriesInfo, resourcesInfo, diplomacyInfo);

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

       objectOutputStream.writeObject(savedGame);

       objectOutputStream.close();
   }
}
Resultaat:

Exception in thread "main" java.io.NotSerializableException: DiplomacyInfo
Het werkte niet! Dat is eigenlijk het antwoord op onze vraag. Wanneer een object is geserialiseerd, worden alle objecten waarnaar wordt verwezen door de instantievariabelen geserialiseerd. En als die objecten ook verwijzen naar andere objecten, dan zijn ze ook geserialiseerd. En zo tot in het oneindige. Alle klassen in deze keten moeten zijnSerializable , anders is het onmogelijk om ze te serialiseren en wordt er een uitzondering gegenereerd. Overigens kan dit later voor problemen zorgen. Wat moeten we doen als we bijvoorbeeld geen deel van een klasse nodig hebben bij het serialiseren? Of, bijvoorbeeld, wat als de TerritoryInfoklas naar ons toe kwam als onderdeel van een bibliotheek van derden. En stel verder dat het niet zo is Serializableen dat we het dus niet kunnen veranderen. Het blijkt dat we geen TerritoryInfoveld kunnen toevoegen aan onzeSavedGameklasse, omdat dit de hele SavedGameklasse niet-serialiseerbaar zou maken! Dat is een probleem :/ Wat is het verschil tussen serialisatie en deserialisatie in Java?  - 2In Java worden dit soort problemen opgelost met behulp van het transienttrefwoord. Als u dit trefwoord toevoegt aan een veld van uw klasse, wordt dat veld niet geserialiseerd. Laten we proberen een van de SavedGameinstantievelden van de klasse van voorbijgaande aard te maken. Vervolgens zullen we één object serialiseren en herstellen.

import java.io.Serializable;

public class SavedGame implements Serializable {

   private transient TerritoriesInfo territoriesInfo;
   private ResourcesInfo resourcesInfo;
   private DiplomacyInfo diplomacyInfo;

   public SavedGame(TerritoriesInfo territoriesInfo, ResourcesInfo resourcesInfo, DiplomacyInfo diplomacyInfo) {
       this.territoriesInfo = territoriesInfo;
       this.resourcesInfo = resourcesInfo;
       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(territoriesInfo, resourcesInfo, 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();


   }
}
En hier is het resultaat:

SavedGame{territoriesInfo=null, resourcesInfo=ResourcesInfo{info='Spain has 100 gold, Russia has 80 gold, France has 90 gold'}, diplomacyInfo=DiplomacyInfo{info='France is at war with Russia, Spain has taken a neutral position'}}
Bovendien kregen we een antwoord op onze vraag welke waarde aan een transientveld wordt toegekend. Het krijgt de standaardwaarde toegewezen. Voor objecten is dit null. U kunt dit uitstekende artikel over serialisatie lezen als u een paar minuten over heeft. Het vermeldt ook de Externalizableinterface, waar we het in de volgende les over zullen hebben. Daarnaast bevat het boek "Head-First Java" een hoofdstuk over dit onderwerp. Geef het wat aandacht :)
Opmerkingen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION