CodeGym /Java Blog /Random-IT /Serializzazione e deserializzazione in Java
John Squirrels
Livello 41
San Francisco

Serializzazione e deserializzazione in Java

Pubblicato nel gruppo Random-IT
CIAO! Nella lezione di oggi parleremo di serializzazione e deserializzazione in Java. Inizieremo con un semplice esempio. Immagina di essere uno sviluppatore di giochi 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!Serializzazione e deserializzazione in Java - 1Temo che un gioco senza queste abilità oggi sarebbe condannato! Ad ogni modo, cosa significa "salvare" e "caricare" una partita? Bene, capiamo il significato quotidiano: vogliamo continuare il gioco dal punto in cui l'abbiamo interrotto. Per fare ciò, creiamo un certo "punto di controllo" che utilizzeremo in seguito per caricare il gioco. Ma cosa significa questo per un programmatore piuttosto che per un giocatore occasionale? La risposta è semplice: salviamo lo stato del nostro programma. Diciamo che stai giocando con la Spagna in un gioco di strategia. Il tuo gioco ha uno stato: quali territori hanno tutti, quante risorse hanno tutti, quali alleanze esistono e con chi, chi è in guerra e così via. Questa informazione, lo stato del nostro programma, deve essere in qualche modo salvata per ripristinare i dati e continuare il gioco. Come succede, La serializzazione in Java è il processo di salvataggio dello stato di un oggetto come sequenza di byte. La deserializzazione in Java è il processo di ripristino di un oggetto da questi byte. Qualsiasi oggetto Java può essere convertito in una sequenza di byte. perché ne abbiamo bisogno? Abbiamo ripetutamente affermato che i programmi non esistono da soli. Molto spesso interagiscono tra loro, si scambiano dati, ecc. Un formato byte è conveniente ed efficiente per questo. Ad esempio, possiamo convertire un oggetto del nostro SavedGameclass in una sequenza di byte, trasferisci questi byte attraverso la rete su un altro computer, e poi sull'altro computer riconverti questi byte in un oggetto Java! Sembra difficile, eh? Sembra difficile che tutto questo accada: / Fortunatamente, non è così! :) In Java, l' interfaccia Serializable è responsabile del processo di serializzazione. Questa interfaccia è estremamente semplice: non devi implementare un solo metodo per usarla! Guarda quanto è semplice la nostra classe per salvare i giochi:

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) +
               '}';
   }
}
Tre array sono responsabili delle informazioni su territori, risorse e diplomazia e l'interfaccia Serializable dice alla macchina Java: " va tutto bene se gli oggetti di questa classe possono essere serializzati ". Un'interfaccia senza una singola interfaccia sembra strana :/ Perché è necessaria? La risposta a questa domanda è data sopra: è necessaria solo per fornire le informazioni necessarie alla macchina Java. In una lezione passata, 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. Ecco Serializable , una di queste interfacce. Ecco un altro punto importante: perché abbiamo bisogno del fileprivate static final long serialVersionUID variabile che abbiamo definito nella classe? Questo campo contiene l'identificatore di versione univoco della classe serializzata. Ogni classe che implementa l' interfaccia Serializable ha un identificatore di versione. Viene determinato in base al contenuto della classe: campi e il loro ordine di dichiarazione, e metodi e il loro ordine di dichiarazione. E se cambiamo un tipo di campo e/o il numero di campi nella nostra classe, l'identificatore di versione cambia istantaneamente. Il serialVersionUID viene scritto anche quando la classe viene serializzata. Quando proviamo a deserializzare, ovvero ripristinare un oggetto da una sequenza di byte, il valore di serialVersionUID viene confrontato con il valore di serialVersionUIDdella classe nel nostro programma. Se i valori non corrispondono, verrà generata un'eccezione java.io.InvalidClassException. Ne vedremo un esempio di seguito. Per evitare tali situazioni, impostiamo semplicemente l'identificatore di versione per la nostra classe manualmente. Nel nostro caso, sarà semplicemente uguale a 1 (puoi usare qualsiasi altro numero che preferisci). Bene, è ora di provare a serializzare il nostro oggetto SavedGame 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[] 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();
   }
}
Come puoi vedere, abbiamo creato 2 flussi: FileOutputStream e ObjectOutputStream . Il primo sa come scrivere i dati nel file e il secondo converte gli oggetti in byte. Hai già visto costrutti nidificati simili, ad esempio, new BufferedReader(new InputStreamReader(...)) , nelle lezioni precedenti, quindi non dovrebbero spaventarti :) Creando questa catena di due flussi, eseguiamo entrambe le attività: convertiamo l' oggetto SavedGame in una sequenza di byte e lo salviamo in un file usando il metodo writeObject() . 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 il nome specificato non esiste, verrà creato automaticamente* Ed ecco il suo contenuto: ¬н sr SavedGame [ diplomacyInfot [Ljava/lang/String;[ resourceInfoq ~ [ territorioInfoq ~ xpur [Ljava.lang. String;¬ТVзй{Sol xp t pФранция воюет СЃ Россией, Р˜СЃРїР°РЅРё СЏ заняла позицию нейтралит етаuq ~ t "РЈ Р˜СЃРїР°РЅРёРё 100 золотаt РЈ Р РѕСЃСЃРёРё 80 золотаt !РЈ Франции 90 Р·РѕР »РѕС‚Р°uq ~ t &РЈ Р˜СЃРїР°РЅРёРё 6 провинцийt %РЈ Р РѕСЃСЃРёРё 10 провинцийt &РЈ Франции 8 провинций Oh, oh :( Sembra che il nostro programma non abbia funzionato : ( In realtà, ha funzionato. Ti ricordi che abbiamo inviato una sequenza di byte, e non semplicemente un oggetto o un testo, al file? Bene, questo è ciò che questa sequenza di byte sembra :) È 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: la deserializzazione. Ecco come appare per noi:

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{territoryInfo=[La Spagna ha 6 province, la Russia ha 10 province, la Francia ha 8 province], resourceInfo=[La Spagna ha 100 ori, la Russia ha 80 ori, la Francia ha 90 ori], diplomacyInfo=[La Francia è in guerra con la Russia, La Spagna ha assunto una posizione neutrale]} Ottimo! 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 rimuoveremo l'identificatore di versione dalla nostra classe SavedGame . Non riscriveremo entrambe le nostre classi. Il loro codice sarà lo stesso. Rimuoveremo solo private static final long serialVersionUID dalla classe SavedGame . Ecco il nostro oggetto dopo la serializzazione: ¬н sr SavedGameі€MіuОm‰ [ diplomacyInfot [Ljava/lang/String;[ resourceInfoq ~ [ territorioInfoq ~ 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: classe locale incompatibile: stream classdesc serialVersionUID = -196410440475012755, classe locale serialVersionUID = -6675950253085108747 A proposito, ci siamo persi qualcosa di importante. Ovviamente, le stringhe e le primitive vengono serializzate facilmente: Java ha certamente un meccanismo integrato per questo. Ma cosa succede se la nostra classe serializzabile ha campi che non sono primitivi, ma piuttosto riferimenti ad altri oggetti? Ad esempio, creiamo una classe TerritoryInfo , ResourceInfo e DiplomacyInfo separata per lavorare con la nostra classe SavedGame .

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 + '\'' +
               '}';
   }
}
E ora ci troviamo di fronte a una domanda: tutte queste classi devono essere serializzabili se vogliamo serializzare la nostra classe SavedGame ?

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 +
               '}';
   }
}
Bene allora! Proviamolo! Per ora, lasceremo tutto così com'è e proveremo a serializzare un 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
       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();
   }
}
Risultato: eccezione nel thread "main" java.io.NotSerializableException: DiplomacyInfo Non ha funzionato! Quindi, ecco 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 ancora e ancora per sempre. Tutte le classi in questa catena devono essere Serializable , altrimenti sarà impossibile serializzarle e verrà generata un'eccezione. A proposito, questo può creare problemi lungo la strada. Ad esempio, cosa dovremmo fare se non abbiamo bisogno di parte di una classe durante la serializzazione? O se ottenessimo la nostra classe TerritoryInfo "attraverso l'ereditarietà" come parte di una libreria? E supponiamo inoltre che non siae, di conseguenza, non possiamo cambiarlo. Ciò significherebbe che non possiamo aggiungere un campo TerritoryInfo alla nostra classe SavedGame , perché l'intera classe SavedGame diventerebbe non serializzabile! Questo è un problema: / Serializzazione e deserializzazione in Java - 2In Java, questo tipo di problema è risolto dalla parola chiave transient . Se aggiungi questa parola chiave a un campo della tua classe, quel campo non verrà serializzato. Proviamo a creare uno dei campi della nostra classe SavedGame transient , quindi serializziamo e ripristiniamo un singolo oggetto.

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


   }
}
Ed ecco il risultato: SavedGame{territoryInfo=null, resourceInfo=ResourceInfo{info='Spain has 100 gold, Russia has 80 gold, France has 90 gold'}, diplomacyInfo=DiplomacyInfo{info='La Francia è in guerra con la Russia, la Spagna ha assunto una posizione neutra'}} Detto questo, abbiamo una risposta alla domanda su quale valore verrà assegnato a un campo transitorio . Viene assegnato il valore predefinito. Per gli oggetti, questo è null . Puoi leggere un eccellente capitolo su questo argomento nel libro 'Head-First Java', prestaci attenzione :)
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION