CodeGym /Blog Java /Random-ES /¿Cuál es la diferencia entre serialización y deserializac...
Autor
Aditi Nawghare
Software Engineer at Siemens

¿Cuál es la diferencia entre serialización y deserialización en Java?

Publicado en el grupo Random-ES
¡Hola! En la lección de hoy, hablamos sobre serialización y deserialización en Java. Comenzaremos con un ejemplo simple. Digamos que has creado un juego de computadora. Si creciste en los años 90 y recuerdas las consolas de juegos de esa época, probablemente sepas que carecían de algo que damos por sentado hoy: la capacidad de guardar y cargar juegos :) Si no, ¡imagínate eso! ¿Cuál es la diferencia entre serialización y deserialización en Java?  - 1 ¡Me temo que hoy en día un juego sin estas habilidades estaría condenado! ¿Qué significa "guardar" y "cargar" un juego de todos modos? Bueno, entendemos el sentido común: queremos continuar el juego desde donde lo dejamos. Para hacer esto, creamos una especie de "punto de control", que luego usamos para cargar el juego. Pero, ¿qué significa esto para un programador en lugar de un jugador casual? La respuesta es simple: nosotros. Digamos que estás jugando como España en Strategium. Tu juego tiene un estado: quién posee qué territorios, quién tiene cuántos recursos, quién está aliado con quién, quién está en guerra con quién, etc. De alguna manera debemos guardar esta información, el estado de nuestro programa, para restaurarlo en el futuro y continuar el juego. Pues para esto precisamente están la serialización y la deserealización . La serialización es el proceso de almacenar el estado de un objeto en una secuencia de bytes. deserializaciónes el proceso de restauración de un objeto a partir de estos bytes. Cualquier objeto Java se puede convertir en una secuencia de bytes. ¿Por qué necesitaríamos eso? Hemos dicho más de una vez que los programas no existen por sí solos. La mayoría de las veces, interactúan con otros programas, intercambian datos, etc. Y una secuencia de bytes es un formato conveniente y eficiente. Por ejemplo, podemos convertir nuestro SavedGameobjeto en una secuencia de bytes, enviar estos bytes a través de la red a otra computadora y luego, en la segunda computadora, convertir estos bytes nuevamente en un objeto Java. Suena difícil, ¿verdad? E implementar este proceso parece un fastidio :/ ¡Felizmente, esto no es así! :) En Java, elSerializableLa interfaz es responsable del proceso de serialización. Esta interfaz es extremadamente simple: ¡no necesita implementar un solo método para usarla! Así de simple se ve nuestra clase de ahorro de juegos:

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) +
               '}';
   }
}
Las tres matrices son responsables de la información sobre territorios, recursos y diplomacia. La interfaz Serializable le dice a la máquina virtual Java: " Todo está bien; si es necesario, los objetos de esta clase se pueden serializar ". Una interfaz sin una sola interfaz se ve rara :/ ¿Por qué es necesaria? La respuesta a esta pregunta se puede ver arriba: solo sirve para proporcionar la información necesaria a la máquina virtual Java. En una de nuestras lecciones anteriores, mencionamos brevemente las interfaces de marcador . Estas son interfaces informativas especiales que simplemente marcan nuestras clases con información adicional que será útil para la máquina Java en el futuro. No tienen ningún método que tengas que implementar.Serializablees una de esas interfaces. Otro punto importante: ¿Por qué necesitamos la private static final long serialVersionUIDvariable que definimos en la clase? ¿Por qué es necesario? Este campo contiene un identificador único para la versión de la clase serializada . Cualquier clase que implemente la Serializableinterfaz tiene un versionidentificador. Se calcula en base a los contenidos de la clase: sus campos, el orden en que se declaran, métodos, etc. Si cambiamos el tipo de campo y/o el número de campos en nuestra clase, inmediatamente cambia el identificador de versión . serialVersionUIDtambién se escribe cuando la clase se serializa. Cuando intentamos deserializar, es decir, restaurar un objeto a partir de un conjunto de bytes, el asociado serialVersionUIDse compara con el valor deserialVersionUIDpara la clase en nuestro programa. Si los valores no coinciden, entonces java.io. Se lanzará InvalidClassException . Veremos un ejemplo de esto a continuación. Para evitar esto, simplemente configuramos el identificador de versión manualmente en nuestra clase. En nuestro caso, simplemente será igual a 1 (pero puedes sustituirlo por cualquier otro número que quieras). Bueno, es hora de intentar serializar nuestro SavedGameobjeto y ver qué sucede.

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();
   }
}
Como puede ver, creamos 2 flujos: FileOutputStreamy ObjectOutputStream. El primero puede escribir datos en un archivo y el segundo convierte objetos en bytes. Ya ha visto construcciones "anidadas" similares, por ejemplo, new BufferedReader(new InputStreamReader(...))en lecciones anteriores, por lo que no deberían asustarlo :) Al crear una "cadena" de este tipo de dos flujos, realizamos ambas tareas: convertimos el SavedGameobjeto en un conjunto de bytes y guárdelo en un archivo usando el writeObject()método. Y, por cierto, ¡ni siquiera miramos lo que obtuvimos! ¡Es hora de mirar el archivo! *Nota: no es necesario que cree el archivo con anterioridad. Si no existe un archivo con ese nombre, se creará automáticamente* ¡ Y aquí está su contenido!

¬н 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 :( Parece que nuestro programa no funcionó :( De hecho, funcionó. ¿Recuerdas que enviamos un conjunto de bytes, no simplemente un objeto o texto, al archivo? Bueno, esto es lo que Este es nuestro juego guardado. Si queremos restaurar nuestro objeto original, es decir, comenzar y continuar el juego donde lo dejamos, entonces necesitamos el proceso inverso: deserialización. Así es como se verá en nuestro 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);
   }
}
¡Y aquí está el resultado!

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]}
¡Excelente! Primero logramos guardar el estado de nuestro juego en un archivo y luego restaurarlo desde el archivo. Ahora intentemos hacer lo mismo, pero sin el identificador de versión de nuestra SavedGameclase. No reescribiremos nuestras dos clases. Su código seguirá siendo el mismo, pero lo eliminaremos private static final long serialVersionUIDde la SavedGameclase. Aquí está nuestro objeto después de la serialización:

¬н 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 провинций
Pero mira lo que sucede cuando tratamos de deserializarlo:

InvalidClassException: local class incompatible: stream classdesc serialVersionUID = -196410440475012755, local class serialVersionUID = -6675950253085108747
Esta es la excepción que mencionamos anteriormente. Por cierto, nos hemos perdido algo importante. Tiene sentido que las cadenas y las primitivas se puedan serializar fácilmente: Java probablemente tenga algún tipo de mecanismo incorporado para hacer esto. Pero, ¿y si nuestra serializableclase tiene campos que no son primitivos, sino referencias a otros objetos? Por ejemplo, creemos clases separadas TerritoriesInfoy para trabajar con nuestra clase. 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 + '\'' +
               '}';
   }
}
Y ahora surge una pregunta: ¿ deben ser todas estas clases Serializablesi queremos serializar nuestra SavedGameclase alterada?

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 +
               '}';
   }
}
Bueno, ¡vamos a probarlo! Dejemos todo como está e intentemos serializar un SavedGameobjeto:

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

Exception in thread "main" java.io.NotSerializableException: DiplomacyInfo
¡No funcionó! Básicamente, esa es la respuesta a nuestra pregunta. Cuando se serializa un objeto, se serializan todos los objetos a los que hacen referencia sus variables de instancia. Y si esos objetos también hacen referencia a otros objetos, también se serializan. Y así hasta el infinito. Todas las clases de esta cadena deben serSerializable , de lo contrario será imposible serializarlas y se lanzará una excepción. Por cierto, esto puede crear problemas en el futuro. ¿Qué debemos hacer si, por ejemplo, no necesitamos parte de una clase cuando serializamos? O, por ejemplo, qué pasaría si la TerritoryInfoclase viniera a nosotros como parte de una biblioteca de terceros. Y supongamos además que no lo es Serializabley, en consecuencia, no podemos cambiarlo. Resulta que no podemos agregar un TerritoryInfocampo a nuestroSavedGameclass, porque hacerlo haría que toda SavedGamela clase no sea serializable. Ese es un problema :/ ¿Cuál es la diferencia entre serialización y deserialización en Java?  - 2En Java, los problemas de este tipo se resuelven usando la transientpalabra clave. Si agrega esta palabra clave a un campo de su clase, ese campo no se serializará. Intentemos hacer que uno de los SavedGamecampos de instancia de la clase sea transitorio. Luego serializaremos y restauraremos un objeto.

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


   }
}
Y aquí está el resultado:

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'}}
Además, obtuvimos una respuesta a nuestra pregunta sobre qué valor se asigna a un transientcampo. Se le asigna el valor predeterminado. Para objetos, esto es null. Puede leer este excelente artículo sobre serialización cuando tenga unos minutos libres. También menciona la Externalizableinterfaz, de la que hablaremos en la siguiente lección. Además, el libro "Head-First Java" tiene un capítulo sobre este tema. Ponle un poco de atención :)
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION