CodeGym /Java Blog /Random-IT /Qual è la differenza tra serializzazione e deserializzazi...
John Squirrels
Livello 41
San Francisco

Qual è la differenza tra serializzazione e deserializzazione in Java?

Pubblicato nel gruppo Random-IT
CIAO! Nella lezione di oggi parliamo di serializzazione e deserializzazione in Java. Inizieremo con un semplice esempio. Diciamo che hai creato un gioco per computer. Se sei cresciuto negli anni '90 e ricordi le console di gioco di quell'epoca, probabilmente saprai che mancava qualcosa che oggi diamo per scontato: la possibilità di salvare e caricare i giochi :) In caso contrario, immagina! Qual è la differenza tra serializzazione e deserializzazione in Java?  - 1 Temo che oggi un gioco senza queste abilità sarebbe condannato! Cosa significa comunque "salvare" e "caricare" una partita? Bene, comprendiamo il significato ordinario: vogliamo continuare il gioco dal punto in cui l'abbiamo interrotto. Per fare ciò, creiamo una sorta di "punto di controllo", che poi utilizziamo per caricare il gioco. Ma cosa significa questo per un programmatore piuttosto che per un giocatore occasionale? La risposta è semplice: noi'. Diciamo che stai giocando con la Spagna in Strategium. Il tuo gioco ha uno stato: chi possiede quali territori, chi ha quante risorse, chi è alleato con chi, chi è in guerra con chi e così via. Dobbiamo in qualche modo salvare queste informazioni, lo stato del nostro programma, per ripristinarle in futuro e continuare il gioco. Perché è proprio a questo che servono la serializzazione e la desrealizzazione . La serializzazione è il processo di memorizzazione dello stato di un oggetto in una sequenza di byte. Deserializzazioneè il processo di ripristino di un oggetto da questi byte. Qualsiasi oggetto Java può essere convertito in una sequenza di byte. Perché ne avremmo bisogno? Abbiamo detto più di una volta che i programmi non esistono da soli. Molto spesso interagiscono con altri programmi, scambiano dati, ecc. E una sequenza di byte è un formato conveniente ed efficiente. Ad esempio, possiamo trasformare il nostro SavedGameoggetto in una sequenza di byte, inviare questi byte attraverso la rete a un altro computer e quindi sul secondo computer trasformare questi byte in un oggetto Java! Sembra difficile, vero? E implementare questo processo sembra una seccatura :/ Fortunatamente, non è così! :) In Java, ilSerializablel'interfaccia è responsabile del processo di serializzazione. Questa interfaccia è estremamente semplice: non è necessario implementare un solo metodo per utilizzarla! Ecco come appare semplice la nostra lezione di salvataggio del gioco:

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) +
               '}';
   }
}
I tre array sono responsabili delle informazioni su territori, risorse e diplomazia. L'interfaccia Serializable dice alla macchina virtuale Java: " Va tutto bene — se necessario, gli oggetti di questa classe possono essere serializzati ". Un'interfaccia senza una singola interfaccia sembra strana :/ Perché è necessaria? La risposta a questa domanda la si vede sopra: serve solo a fornire le informazioni necessarie alla macchina virtuale Java. In una delle nostre lezioni precedenti, abbiamo accennato brevemente alle interfacce marker . Queste sono interfacce informative speciali che contrassegnano semplicemente le nostre classi con informazioni aggiuntive che saranno utili alla macchina Java in futuro. Non hanno alcun metodo che devi implementare.Serializableè una di quelle interfacce. Un altro punto importante: perché abbiamo bisogno della private static final long serialVersionUIDvariabile che abbiamo definito nella classe? Perché è necessario? Questo campo contiene un identificatore univoco per la versione della classe serializzata . Ogni classe che implementa l' Serializableinterfaccia ha un versionidentificatore. Viene calcolato in base al contenuto della classe: i suoi campi, l'ordine in cui sono dichiarati, i metodi, ecc. Se cambiamo il tipo di campo e/o il numero di campi nella nostra classe, allora l'identificatore di versione cambia immediatamente . serialVersionUIDviene scritto anche quando la classe viene serializzata. Quando proviamo a deserializzare, ovvero ripristinare un oggetto da un insieme di byte, l'associato serialVersionUIDviene confrontato con il valore diserialVersionUIDper la classe nel nostro programma. Se i valori non corrispondono, allora un java.io. Verrà generata un'eccezione InvalidClassException . Ne vedremo un esempio di seguito. Per evitare ciò, impostiamo semplicemente manualmente l'identificatore di versione nella nostra classe. Nel nostro caso, sarà semplicemente uguale a 1 (ma puoi sostituirlo con qualsiasi altro numero che preferisci). Bene, è ora di provare a serializzare il nostro SavedGameoggetto e vedere cosa succede!

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();
   }
}
Come puoi vedere, abbiamo creato 2 flussi: FileOutputStreame ObjectOutputStream. Il primo può scrivere dati su un file e il secondo converte gli oggetti in byte. Hai già visto simili costrutti "nidificati", ad esempio, new BufferedReader(new InputStreamReader(...))nelle lezioni precedenti, quindi non dovrebbero spaventarti :) Creando una tale "catena" di due flussi, eseguiamo entrambi i compiti: convertiamo l' SavedGameoggetto in un insieme di byte e salvarlo in un file utilizzando il writeObject()metodo. E, a proposito, non abbiamo nemmeno guardato quello che abbiamo! È ora di guardare il file! *Nota: non è necessario creare il file in anticipo. Se un file con quel nome non esiste, verrà creato automaticamente* Ed ecco il suo contenuto!

¬н 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 :( Sembra che il nostro programma non abbia funzionato :( In effetti, ha funzionato. Ricordi che abbiamo inviato una serie di byte, non solo un oggetto o un testo, al file? Bene, questo è ciò che set di byte sembra :) Questo è il nostro gioco salvato!Se vogliamo ripristinare il nostro oggetto originale, ovvero iniziare e continuare il gioco da dove l'avevamo interrotto, allora abbiamo bisogno del processo inverso: deserializzazione. Ecco come apparirà nel nostro caso:

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);
   }
}
Ed ecco il risultato!

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]}
Eccellente! Siamo riusciti prima a salvare lo stato del nostro gioco in un file, quindi a ripristinarlo dal file. Ora proviamo a fare la stessa cosa, ma senza l'identificatore di versione per la nostra SavedGameclasse. Non riscriveremo entrambe le nostre classi. Il loro codice rimarrà lo stesso, ma lo rimuoveremo private static final long serialVersionUIDdalla SavedGameclasse. Ecco il nostro oggetto dopo la serializzazione:

¬н 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 провинций
Ma guarda cosa succede quando proviamo a deserializzarlo:

InvalidClassException: local class incompatible: stream classdesc serialVersionUID = -196410440475012755, local class serialVersionUID = -6675950253085108747
Questa è proprio l'eccezione che abbiamo menzionato sopra. A proposito, ci siamo persi qualcosa di importante. Ha senso che le stringhe e le primitive possano essere facilmente serializzate: Java probabilmente ha una sorta di meccanismo integrato per farlo. Ma cosa succede se la nostra serializableclasse ha campi che non sono primitivi, ma piuttosto riferimenti ad altri oggetti? Ad esempio, creiamo classi separate TerritoriesInfoe per lavorare con la nostra classe. ResourcesInfoDiplomacyInfoSavedGame

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 + '\'' +
               '}';
   }
}
E ora sorge una domanda: tutte queste classi devono essere Serializablese vogliamo serializzare la nostra SavedGameclasse modificata?

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 +
               '}';
   }
}
Bene, proviamolo! Lasciamo tutto com'è e proviamo a serializzare un SavedGameoggetto:

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();
   }
}
Risultato:

Exception in thread "main" java.io.NotSerializableException: DiplomacyInfo
Non ha funzionato! Fondamentalmente, questa è la risposta alla nostra domanda. Quando un oggetto viene serializzato, vengono serializzati tutti gli oggetti a cui fanno riferimento le sue variabili di istanza. E se quegli oggetti fanno riferimento anche ad altri oggetti, anche loro vengono serializzati. E così all'infinito. Tutte le classi in questa catena devono essereSerializable , altrimenti sarà impossibile serializzarle e verrà generata un'eccezione. A proposito, questo può creare problemi lungo la strada. Cosa dovremmo fare se, ad esempio, non abbiamo bisogno di parte di una classe quando serializziamo? O, ad esempio, cosa succederebbe se la TerritoryInfoclasse arrivasse da noi come parte di una libreria di terze parti. E supponiamo inoltre che non lo sia Serializablee, di conseguenza, non possiamo cambiarlo. Si scopre che non possiamo aggiungere un TerritoryInfocampo a ourSavedGameclass, perché così facendo l'intera SavedGameclasse non sarebbe serializzabile! Questo è un problema :/ Qual è la differenza tra serializzazione e deserializzazione in Java?  - 2In Java, problemi di questo tipo vengono risolti utilizzando la transientparola chiave. Se aggiungi questa parola chiave a un campo della tua classe, quel campo non verrà serializzato. Proviamo a rendere SavedGametransitorio uno dei campi istanza della classe. Quindi serializziamo e ripristiniamo un oggetto.

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();


   }
}
Ed ecco il risultato:

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'}}
Inoltre, abbiamo ottenuto una risposta alla nostra domanda su quale valore viene assegnato a un transientcampo. Viene assegnato il valore predefinito. Per gli oggetti, questo è null. Puoi leggere questo eccellente articolo sulla serializzazione quando hai qualche minuto libero. Menziona anche l' Externalizableinterfaccia, di cui parleremo nella prossima lezione. Inoltre, il libro "Head-First Java" ha un capitolo su questo argomento. Dategli un po' di attenzione :)
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION