CodeGym/Blog Java/Random-FR/Quelle est la différence entre la sérialisation et la dés...
Auteur
Aditi Nawghare
Software Engineer at Siemens

Quelle est la différence entre la sérialisation et la désérialisation en Java ?

Publié dans le groupe Random-FR
membres
Salut! Dans la leçon d'aujourd'hui, nous parlons de sérialisation et de désérialisation en Java. Nous allons commencer par un exemple simple. Disons que vous avez créé un jeu vidéo. Si vous avez grandi dans les années 90 et que vous vous souvenez des consoles de jeux de cette époque, vous savez probablement qu'il leur manquait quelque chose que nous tenons pour acquis aujourd'hui : la possibilité de sauvegarder et de charger des jeux :) Sinon, imaginez ça ! Quelle est la différence entre la sérialisation et la désérialisation en Java ?  - 1 J'ai bien peur qu'aujourd'hui un jeu sans ces capacités soit voué à l'échec ! Qu'est-ce que cela signifie de "sauvegarder" et de "charger" un jeu de toute façon ? Eh bien, nous comprenons le sens ordinaire : nous voulons continuer le jeu là où nous l'avons laissé. Pour ce faire, nous créons une sorte de "point de contrôle", que nous utilisons ensuite pour charger le jeu. Mais qu'est-ce que cela signifie pour un programmeur plutôt que pour un joueur occasionnel ? La réponse est simple : nous. Disons que vous jouez avec l'Espagne dans Strategium. Votre jeu a un état : qui possède quels territoires, qui a combien de ressources, qui est dans une alliance avec qui, qui est en guerre avec qui, etc. Nous devons en quelque sorte sauvegarder cette information, l'état de notre programme, afin de la restaurer à l'avenir et continuer le jeu. Car c'est précisément à cela que servent la sérialisation et la déséréalisation . La sérialisation est le processus de stockage de l'état d'un objet dans une séquence d'octets. Désérialisationest le processus de restauration d'un objet à partir de ces octets. Tout objet Java peut être converti en une séquence d'octets. Pourquoi aurions-nous besoin de cela ? Nous avons dit plus d'une fois que les programmes n'existent pas seuls. Le plus souvent, ils interagissent avec d'autres programmes, échangent des données, etc. Et une séquence d'octets est un format pratique et efficace. Par exemple, nous pouvons transformer notre SavedGameobjet en une séquence d'octets, envoyer ces octets sur le réseau à un autre ordinateur, puis, sur le deuxième ordinateur, transformer ces octets en un objet Java ! Cela semble difficile, non ? Et la mise en œuvre de ce processus semble pénible :/ Heureusement, ce n'est pas le cas ! :) En Java, leSerializableinterface est responsable du processus de sérialisation. Cette interface est extrêmement simple : vous n'avez pas besoin d'implémenter une seule méthode pour l'utiliser ! Voici à quel point notre classe de sauvegarde de jeu est simple :
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) +
               '}';
   }
}
Les trois tableaux sont responsables des informations sur les territoires, les ressources et la diplomatie. L'interface Serializable indique à la machine virtuelle Java : " Tout va bien — si nécessaire, les objets de cette classe peuvent être sérialisés ". Une interface sans une seule interface a l'air bizarre :/ Pourquoi est-ce nécessaire ? La réponse à cette question se trouve ci-dessus : elle ne sert qu'à fournir les informations nécessaires à la machine virtuelle Java. Dans l'une de nos leçons précédentes, nous avons brièvement mentionné les interfaces de marqueur . Ce sont des interfaces d'information spéciales qui marquent simplement nos classes avec des informations supplémentaires qui seront utiles à la machine Java à l'avenir. Ils n'ont pas de méthodes que vous devez mettre en œuvre.Serializableest l'une de ces interfaces. Autre point important : Pourquoi avons-nous besoin de la private static final long serialVersionUIDvariable que nous avons définie dans la classe ? Pourquoi est-ce nécessaire ? Ce champ contient un identifiant unique pour la version de la classe sérialisée . Toute classe qui implémente l' Serializableinterface a un versionidentifiant. Il est calculé en fonction du contenu de la classe : ses champs, l'ordre dans lequel ils sont déclarés, les méthodes, etc. Si on change le type de champ et/ou le nombre de champs dans notre classe, alors l'identifiant de version change immédiatement . serialVersionUIDest également écrit lorsque la classe est sérialisée. Lorsque nous essayons de désérialiser, c'est-à-dire de restaurer un objet à partir d'un ensemble d'octets, l'associé serialVersionUIDest comparé à la valeur deserialVersionUIDpour la classe de notre programme. Si les valeurs ne correspondent pas, alors un fichier java.io. InvalidClassException sera levée. Nous en verrons un exemple ci-dessous. Pour éviter cela, nous définissons simplement l'identifiant de version manuellement dans notre classe. Dans notre cas, il sera simplement égal à 1 (mais vous pouvez remplacer n'importe quel autre nombre que vous aimez). Eh bien, il est temps d'essayer de sérialiser notre SavedGameobjet et de voir ce qui se passe !
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();
   }
}
Comme vous pouvez le voir, nous avons créé 2 flux : FileOutputStreamet ObjectOutputStream. Le premier peut écrire des données dans un fichier et le second convertit les objets en octets. Vous avez déjà vu des constructions "imbriquées" similaires, par exemple, new BufferedReader(new InputStreamReader(...)), dans les leçons précédentes, donc celles-ci ne devraient pas vous effrayer :) En créant une telle "chaîne" de deux flux, nous effectuons les deux tâches : nous convertissons l' SavedGameobjet en un ensemble d'octets et enregistrez-le dans un fichier à l'aide de la writeObject()méthode. Et, soit dit en passant, nous n'avons même pas regardé ce que nous avons eu ! Il est temps de regarder le dossier ! *Remarque : vous n'avez pas besoin de créer le fichier à l'avance. Si un fichier portant ce nom n'existe pas, il sera créé automatiquement* Et voici son contenu !
¬н 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 :( Il semble que notre programme n'a pas fonctionné :( En fait, il a fonctionné. Vous souvenez-vous que nous avons envoyé un ensemble d'octets, pas simplement un objet ou un texte, au fichier ? jeu d'octets ressemble à :) Ceci est notre partie sauvegardée ! Si nous voulons restaurer notre objet d'origine, c'est-à-dire démarrer et continuer le jeu là où nous l'avons laissé, nous avons besoin du processus inverse : désérialisation . Voici à quoi cela ressemblera dans notre cas:
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);
   }
}
Et voici le résultat !
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]}
Excellent! Nous avons réussi à enregistrer d'abord l'état de notre jeu dans un fichier, puis à le restaurer à partir du fichier. Essayons maintenant de faire la même chose, mais sans l'identifiant de version de notre SavedGameclasse. Nous ne réécrirons pas nos deux classes. Leur code restera le même, mais nous le supprimerons private static final long serialVersionUIDde la SavedGameclasse. Voici notre objet après sérialisation :
¬н 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 провинций
Mais regardez ce qui se passe lorsque nous essayons de le désérialiser :
InvalidClassException: local class incompatible: stream classdesc serialVersionUID = -196410440475012755, local class serialVersionUID = -6675950253085108747
C'est l'exception que nous avons mentionnée ci-dessus. Soit dit en passant, nous avons raté quelque chose d'important. Il est logique que les chaînes et les primitives puissent être facilement sérialisées : Java a probablement une sorte de mécanisme intégré pour le faire. Mais que se passe-t-il si notre serializableclasse a des champs qui ne sont pas des primitifs, mais plutôt des références à d'autres objets ? Par exemple, créons des classes et TerritoriesInfoséparées pour travailler avec notre 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 + '\'' +
               '}';
   }
}
Et maintenant, une question se pose : toutes ces classes doivent-elles l'être Serializablesi nous voulons sérialiser notre SavedGameclasse modifiée ?
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 +
               '}';
   }
}
Eh bien, testons-le! Laissons tout tel quel et essayons de sérialiser un SavedGameobjet :
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();
   }
}
Résultat:
Exception in thread "main" java.io.NotSerializableException: DiplomacyInfo
Ça n'a pas marché ! En gros, c'est la réponse à notre question. Lorsqu'un objet est sérialisé, tous les objets référencés par ses variables d'instance sont sérialisés. Et si ces objets font également référence à d'autres objets, ils sont également sérialisés. Et ainsi de suite à l'infini. Toutes les classes de cette chaîne doivent êtreSerializable , sinon il sera impossible de les sérialiser et une exception sera levée. Soit dit en passant, cela peut créer des problèmes sur la route. Que devons-nous faire si, par exemple, nous n'avons pas besoin d'une partie d'une classe lorsque nous sérialisons ? Ou, par exemple, que se passe-t-il si la TerritoryInfoclasse nous est parvenue dans le cadre d'une bibliothèque tierce. Et supposons en outre que ce n'est pas le cas Serializableet, par conséquent, nous ne pouvons pas le changer. Il s'avère que nous ne pouvons pas ajouter de TerritoryInfochamp à notreSavedGameclass, car cela rendrait toute la SavedGameclasse non sérialisable ! C'est un problème :/ Quelle est la différence entre la sérialisation et la désérialisation en Java ?  - 2En Java, les problèmes de ce genre sont résolus à l'aide du transientmot-clé. Si vous ajoutez ce mot-clé à un champ de votre classe, ce champ ne sera pas sérialisé. Essayons de rendre l'un des SavedGamechamps d'instance de la classe transitoire. Ensuite, nous sérialiserons et restaurerons un objet.
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();


   }
}
Et voici le résultat :
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'}}
De plus, nous avons obtenu une réponse à notre question sur la valeur attribuée à un transientchamp. Il obtient la valeur par défaut. Pour les objets, c'est null. Vous pouvez lire cet excellent article sur la sérialisation lorsque vous avez quelques minutes à perdre. Il mentionne également l' Externalizableinterface, dont nous parlerons dans la prochaine leçon. De plus, le livre "Head-First Java" contient un chapitre sur ce sujet. Accordez-lui un peu d'attention :)
Commentaires
  • Populaires
  • Nouveau
  • Anciennes
Tu dois être connecté(e) pour laisser un commentaire
Cette page ne comporte pas encore de commentaires