JavaRush /Java Blog /Random-IT /Serializzazione e deserializzazione in Java

Serializzazione e deserializzazione in Java

Pubblicato nel gruppo Random-IT
Ciao! Nella lezione di oggi parleremo di serializzazione e deserializzazione in Java. Cominciamo con un semplice esempio. Diciamo che sei il creatore di un gioco per computer. Se sei cresciuto negli anni '90 e ricordi le console di gioco di quei tempi, probabilmente sai che oggi mancavano qualcosa di ovvio: salvare e caricare i giochi :) Altrimenti... immagina! Temo che oggi un gioco senza tale opportunità sarà destinato al fallimento! E, in realtà, cosa significa “salvare” e “caricare” un gioco? Ebbene, nel senso comune, capiamo di cosa si tratta: vogliamo continuare il gioco da dove lo avevamo interrotto l'ultima volta. Per fare ciò creiamo una sorta di “checkpoint”, che poi utilizziamo per caricare il gioco. Ma cosa significa questo, non nel senso quotidiano, ma nel senso del “programmatore”? La risposta è semplice: salviamo lo stato del nostro programma. Diciamo che stai giocando a un gioco di strategia per la Spagna. Il tuo gioco ha uno stato: chi possiede quali territori, chi ha quante risorse, chi è alleato con chi e chi, al contrario, è in guerra e così via. Queste informazioni, lo stato del nostro programma, devono essere salvate in qualche modo per poter successivamente ripristinare i dati e continuare il gioco. È proprio a questo che servono i meccanismi di serializzazione e deserializzazione . La serializzazione è il processo di memorizzazione dello stato di un oggetto in una sequenza di byte. La deserializzazione è il processo di ricostruzione di un oggetto da questi byte. Qualsiasi oggetto Java viene convertito in una sequenza di byte. Cosa serve? Abbiamo detto più di una volta che i programmi non esistono da soli. Molto spesso interagiscono tra loro, si scambiano dati, ecc. E il formato byte è conveniente ed efficiente per questo. Possiamo, ad esempio, trasformare il nostro oggetto di classe SavedGame(un gioco salvato) in una sequenza di byte, trasferire quei byte attraverso la rete a un altro computer e sul secondo computer trasformare nuovamente quei byte in un oggetto Java! È difficile da ascoltare, vero? A quanto pare, organizzare questo processo non sarà facile :/ Fortunatamente no! :) In Java, l'interfaccia Serializable è responsabile dei processi di serializzazione . Questa interfaccia è estremamente semplice: non è necessario implementare un solo metodo per utilizzarla! Ecco come apparirà la nostra classe di salvataggio:
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) +
               '}';
   }
}
Tre set di dati sono responsabili delle informazioni su territori, economia e diplomazia, e l'interfaccia Serializable dice alla macchina Java: " va tutto bene, semmai gli oggetti di questa classe possono essere serializzati ". Un'interfaccia che non ha metodi sembra strana:/ Perché è necessaria? La risposta a questa domanda è sopra: solo per fornire le informazioni necessarie alla macchina Java. In una delle lezioni precedenti abbiamo accennato brevemente alle interfacce dei marcatori. Si tratta di speciali interfacce informative che semplicemente contrassegnano le nostre classi con informazioni aggiuntive che saranno utili alla macchina Java in futuro. Non hanno metodi che debbano essere implementati. Quindi, Serializable è una di queste interfacce. Altro punto importante: la variabile private static final long serialVersionUIDche abbiamo definito nella classe. Perché è necessario? Questo campo contiene l'identificatore di versione univoco della classe serializzata . Qualsiasi classe che implementa l'interfaccia Serializable ha un identificatore di versione. Viene calcolato in base al contenuto della classe: campi, ordine di dichiarazione, metodi. E se cambiamo il tipo di campo e/o il numero di campi nella nostra classe, l'identificatore di versione cambierà immediatamente. serialVersionUID viene scritto anche quando la classe viene serializzata. Quando proviamo a deserializzare, cioè a ripristinare un oggetto da un insieme di byte, il valore serialVersionUIDviene confrontato con il valore serialVersionUIDdella classe nel nostro programma. Se i valori non corrispondono, verrà lanciata una java.io.InvalidClassException. Ne vedremo un esempio qui sotto. Per evitare tali situazioni, impostiamo semplicemente manualmente questo ID di versione per la nostra classe. Nel nostro caso sarà semplicemente uguale a 1 (puoi sostituirlo con qualsiasi altro numero che preferisci). Bene, è ora di provare a serializzare il nostro oggetto SavedGamee 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 position of neutrality"};

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

       //create 2 threads 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 game to file
       objectOutputStream.writeObject(savedGame);

       // close the stream and release resources
       objectOutputStream.close();
   }
}
Come puoi vedere, abbiamo creato 2 thread: FileOutputStreame ObjectOutputStream. Il primo può scrivere dati in un file e il secondo può convertire gli oggetti in byte. Hai già visto costruzioni simili "nidificate", ad esempio, new BufferedReader(new InputStreamReader(...))nelle lezioni precedenti, quindi non dovrebbero spaventarti :) Creando una tale "catena" di due thread, eseguiamo entrambi i compiti: trasformiamo l'oggetto SavedGamein un insieme di byte e salvarlo in un file utilizzando il metodo writeObject(). E, a proposito, non abbiamo nemmeno controllato cosa abbiamo ottenuto! È 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 GiocoSalvato [ diplomacyInfot [Ljava/lang/String;[ resourcesInfoq ~ [ territoriInfoq ~ xpur [Ljava.lang.String;ТVзй{G xp t pФранцвоюРμС‚ SЃ R РѕСЃСЃРёРμ Р№, Р˜С ЃРїР° РЅРёСЏ Р·Р°Ряла позицию РЅРμйтралитРμтаuq ~ t "РЈ Р˜ спании 100 золотаt Р J R РѕСЃСЃРёРё 80 золотаt !РЈ Франции 90 золотаuq ~ t &РЈ Р˜ 6 punti †РёР№t %РЈ Р РѕСЃСЃРёРё 10 punti †РёР№t &РЈ ФранцРеРё 8 проввинций Oops :( Sembra che il nostro programma non abbia funzionato :( In realtà ha funzionato. Ti ricordi che abbiamo trasferito esattamente un set di byte a il file e non solo un oggetto o un testo? Bene, ecco come appare questo set :) Questo è il nostro gioco salvato! Se vogliamo ripristinare il nostro oggetto originale, cioè caricare e continuare il gioco da dove lo avevamo interrotto, 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);
   }
}
E questo è il risultato! SavedGame{territoriesInfo=[La Spagna ha 6 province, la Russia ha 10 province, la Francia ha 8 province], resourcesInfo=[La Spagna ha 100 oro, la Russia ha 80 oro, la Francia ha 90 oro], diplomacyInfo=[La Francia è in guerra con la Russia, La Spagna ha occupato una posizione di neutralità]} Fantastico! Siamo riusciti prima a salvare lo stato del nostro gioco in un file e poi a ripristinarlo dal file. Ora proviamo a fare lo stesso, ma rimuoviamo SavedGamel'identificatore di versione dalla nostra classe. Non riscriveremo entrambe le nostre classi, il codice in esse contenuto sarà lo stesso, rimuoveremo SavedGamesemplicemente private static final long serialVersionUID. Ecco il nostro oggetto dopo la serializzazione: ¬н sr SavedGameі€MіuОm‰ [ diplomacyInfot [Ljava/lang/String;[ resourcesInfoq ~ [ territoriInfoq ~ xpur [Ljava.lang.String;ТВзй{G xp t pФранция РІРѕСЋРμ С ‚ СЃ Р РѕСЃСЃРёРμР№, Р˜СЃРїР°РЅРЏР·Р°РЅСЏР»Р° RїРѕР·РёС†РёСЋ РЅРμР№С ‚ралитРμтаuq ~ t "РЈ Р˜СЃРї ании 100 Р · олотаt РЈ Р РѕСЃСЃРёРё 80 золотаt !РЈ Франции 90 Р·Рѕ лотаuq ~ t &РЈ Р˜СЃРїР°РЅРёРё 6 R їСЂРѕРІРёРЅС† РёР№t %РЈ Р РѕСЃСЃРёРё 10 провнинцийt &РЈ Р ¤СЂР°РЅС†РёРё 8 проввинцинА E quando si tenta di deserializzarlo, questo è quello che è successo: InvalidClassException: local class independent : stream classdesc serialVersionUID = - 196410440475012755, classe locale serialVersionUID = -6675950253085108747 Questa è la stessa eccezione menzionata sopra. Puoi leggere di più sull'argomento nell'articolo di uno dei nostri studenti. A proposito, abbiamo mancato un punto importante. È chiaro che stringhe e primitive sono facilmente serializzati: Java sicuramente ne ha alcuni, quindi esistono meccanismi integrati per questo. Ma cosa succederebbe se la nostra serializableclasse avesse campi espressi non come primitivi, ma come riferimenti ad altri oggetti? Creiamo, ad esempio, classi separate TerritoriesInfoper 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 + '\'' +
               '}';
   }
}
Ma ora abbiamo una domanda: tutte queste classi dovrebbero essere Serializable se vogliamo serializzare la classe modificata 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 +
               '}';
   }
}
Bene, controlliamolo in pratica! Lasciamo tutto com'è per ora e proviamo a serializzare l'oggetto SavedGame:
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
       TerritoriesInfo territoriesInfo = new TerritoriesInfo("Spain has 6 provinces, Russia has 10 provinces, France has 8 provinces");
       ResourcesInfo resourcesInfo = new ResourcesInfo("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 position of neutrality");


       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: eccezione nel thread "main" java.io.NotSerializableException: DiplomacyInfo Failed! In realtà, ecco la risposta alla nostra domanda. Quando serializzi un oggetto, tutti gli oggetti a cui fa riferimento nelle sue variabili di istanza vengono serializzati. E se tali oggetti fanno riferimento anche a terzi oggetti, anch'essi vengono serializzati. E così via all'infinito. Tutte le classi in questa catena devono essere Serializable, altrimenti non saranno serializzabili e verrà generata un'eccezione. Questo, tra l'altro, potrebbe creare problemi in futuro. Cosa dovremmo fare, ad esempio, se non abbiamo bisogno di parte della classe durante la serializzazione? Oppure, ad esempio, TerritoryInfoabbiamo ereditato una classe nel nostro programma come parte di una libreria. Tuttavia, non è serializzabile e, di conseguenza, non possiamo modificarlo. Si scopre che non possiamo aggiungere un campo TerritoryInfoalla nostra classe , perché in tal caso l'intera classe diventerà non serializzabile! Problema:/ Problemi di questo tipo vengono risolti in Java utilizzando la parola chiave . Se aggiungi questa parola chiave a un campo di classe, il valore di questo campo non verrà serializzato. Proviamo a creare uno dei campi della nostra classe , dopodiché serializzeremo e ripristineremo un oggetto. SavedGameSavedGameSerializzazione e deserializzazione in Java - 2transientSavedGame transient
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
       TerritoriesInfo territoriesInfo = new TerritoriesInfo("Spain has 6 provinces, Russia has 10 provinces, France has 8 provinces");
       ResourcesInfo resourcesInfo = new ResourcesInfo("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 position of neutrality");


       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='La Spagna ha 100 oro, la Russia ha 80 oro, la Francia ha 90 oro'}, diplomacyInfo=DiplomacyInfo{info='La Francia è in guerra con la Russia, La Spagna ha assunto una posizione neutrale'}} Allo stesso tempo, abbiamo ricevuto una risposta alla domanda su quale valore verrà assegnato transiental campo -. Gli viene assegnato un valore predefinito. Nel caso degli oggetti questo è null. A tuo piacimento, puoi leggere questo eccellente articolo sulla serializzazione . Si parla anche dell'interfaccia Externalizable, di cui parleremo nella prossima lezione. Inoltre, c'è un capitolo su questo argomento nel libro "Head-First Java", presta attenzione :)
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION