¡Hola! En la lección de hoy, hablaremos sobre serialización y deserialización en Java. Comenzaremos con un ejemplo simple. Imagina que eres un desarrollador de juegos 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!
¡Me temo que un juego sin estas habilidades hoy en día estaría condenado! De todos modos, ¿qué significa 'guardar' y 'cargar' un juego? Bueno, entendemos el significado cotidiano: queremos continuar el juego desde el lugar donde lo dejamos. Para hacer esto, creamos un cierto 'punto de control' que usamos más tarde para cargar el juego. Pero, ¿qué significa eso para un programador en lugar de un jugador casual? La respuesta es simple: guardamos el estado de nuestro programa. Digamos que estás jugando España en un juego de estrategia. Tu juego tiene estado: qué territorios tiene cada uno, cuántos recursos tiene cada uno, qué alianzas existen y con quién, quién está en guerra, etc. Esta información, el estado de nuestro programa, debe guardarse de alguna manera para restaurar los datos y continuar el juego. Como sucede, La serialización en Java es el proceso de guardar el estado de un objeto como una secuencia de bytes. La deserialización en Java es el proceso de restaurar un objeto a partir de estos bytes. Cualquier objeto Java se puede convertir en una secuencia de bytes. ¿Porqué necesitamos esto? Hemos dicho repetidamente que los programas no existen por sí mismos. La mayoría de las veces, interactúan entre sí, intercambian datos, etc. Un formato de bytes es conveniente y eficiente para esto. Por ejemplo, podemos convertir un objeto de nuestro SavedGameclass en una secuencia de bytes, transfiera estos bytes a través de la red a otra computadora y luego, en la otra computadora, ¡convierta estos bytes nuevamente en un objeto Java! Suena difícil, ¿eh? Parece que sería difícil hacer que todo esto suceda: / ¡Felizmente, ese no es el caso! :) En Java, la interfaz serializable es responsable del proceso de serialización. Esta interfaz es extremadamente simple: ¡No tiene que implementar un solo método para usarla! Mira lo simple que es nuestra clase para guardar partidas:
En Java, este tipo de problema se resuelve con la palabra clave transient . Si agrega esta palabra clave a un campo de su clase, ese campo no se serializará. Intentemos hacer que uno de los campos de nuestra clase SavedGame sea transitorio , y luego serializaremos y restauraremos un solo objeto.
![Serialización y deserialización en Java - 1](https://cdn.codegym.cc/images/article/a9b24d61-3a21-488a-a2b7-d97bee062ce4/800.jpeg)
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) +
'}';
}
}
Tres matrices son responsables de la información sobre territorios, recursos y diplomacia, y la interfaz Serializable le dice a la máquina Java: ' todo está bien si los objetos de esta clase se pueden serializar '. Una interfaz sin una sola interfaz se ve rara :/ ¿Por qué es necesaria? La respuesta a esa pregunta se da arriba: solo se necesita proporcionar la información necesaria a la máquina Java. En una lección anterior, 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. Aquí está Serializable , una de esas interfaces. Aquí hay otro punto importante: ¿Por qué necesitamos elprivada estática final larga serialVersionUID variable que definimos en la clase? Este campo contiene el identificador de versión único de la clase serializada. Cada clase que implementa la interfaz serializable tiene un identificador de versión. Se determina en función del contenido de la clase: campos y su orden de declaración, y métodos y su orden de declaración. Y si cambiamos un tipo de campo y/o la cantidad de campos en nuestra clase, el identificador de versión cambia instantáneamente. El serialVersionUID también se escribe cuando se serializa la clase. Cuando intentamos deserializar, es decir, restaurar un objeto de una secuencia de bytes, el valor de serialVersionUID se compara con el valor de serialVersionUIDde la clase en nuestro programa. Si los valores no coinciden, se generará una java.io.InvalidClassException. Veremos un ejemplo de esto a continuación. Para evitar tales situaciones, simplemente configuramos el identificador de versión para nuestra clase manualmente. En nuestro caso, simplemente será igual a 1 (puede usar cualquier otro número que desee). Bueno, es hora de intentar serializar nuestro objeto SavedGame 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[] 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();
}
}
Como puede ver, creamos 2 flujos: FileOutputStream y ObjectOutputStream . El primero sabe cómo escribir datos en el 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 esta cadena de dos flujos, realizamos ambas tareas: convertimos el objeto SavedGame en una secuencia de bytes y lo guardamos en un archivo usando el método writeObject() . Y, por cierto, ¡ni siquiera miramos lo que obtuvimos! ¡Es hora de mirar el archivo! *Nota: no es necesario crear el archivo previamente. Si no existe un archivo con el nombre especificado, se creará automáticamente* Y aquí está su contenido: ¬н sr SavedGame [ 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 años Oh, oh :( Parece que nuestro programa no funcionó : ( En realidad, funcionó. ¿Recuerdas que enviamos una secuencia de bytes, y no simplemente un objeto o texto, al archivo? Bueno, esto es lo que esta secuencia de bytes parece :) ¡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 ve para nosotros:
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{territoryInfo=[España tiene 6 provincias, Rusia tiene 10 provincias, Francia tiene 8 provincias], resourceInfo=[España tiene 100 de oro, Rusia tiene 80 de oro, Francia tiene 90 de oro], diplomacyInfo=[Francia está en guerra con Rusia, España ha tomado una posición neutral]} ¡ Excelente! Primero logramos guardar el estado de nuestro juego en un archivo y luego restaurarlo desde el archivo. Ahora intentemos hacer lo mismo, pero eliminaremos el identificador de versión de nuestra clase SavedGame . No reescribiremos nuestras dos clases. Su código será el mismo. Simplemente eliminaremos el serialVersionUID largo final estático privado de la clase SavedGame . Aquí está nuestro objeto después de la serialización: ¬н 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 РїСЂРѕРІРРІР ёРЅС†РёР№ Pero mire lo que sucede cuando intentamos deserializarlo: InvalidClassException: clase local incompatible: stream classdesc serialVersionUID = -196410440475012755, clase local serialVersionUID = -6675950253085108747 Por cierto, nos hemos perdido algo importante. Obviamente, las cadenas y los primitivos se serializan fácilmente: Java ciertamente tiene algún mecanismo incorporado para esto. Pero, ¿y si nuestra clase serializable tiene campos que no son primitivos, sino referencias a otros objetos? Por ejemplo, creemos clases separadas TerritoryInfo , ResourceInfo y DiplomacyInfo para trabajar con nuestra clase 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 + '\'' +
'}';
}
}
Y ahora nos enfrentamos a una pregunta: ¿Todas estas clases deben ser Serializables si queremos serializar nuestra clase de Juego Guardado ?
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 +
'}';
}
}
¡Bien entonces! ¡Vamos a probarlo! Por ahora, dejaremos todo como está e intentaremos serializar un objeto 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();
}
}
Resultado: excepción en el subproceso "principal" java.io.NotSerializableException: DiplomacyInfo ¡No funcionó! Entonces, aquí está 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 una y otra vez para siempre. Todas las clases en esta cadena deben ser Serializables , de lo contrario será imposible serializarlas y se lanzará una excepción. Por cierto, esto puede crear problemas en el futuro. Por ejemplo, ¿qué debemos hacer si no necesitamos parte de una clase durante la serialización? ¿O qué sucede si obtenemos nuestra clase TerritoryInfo 'a través de la herencia' como parte de una biblioteca? Y supongamos además que no esy, en consecuencia, no podemos cambiarlo. ¡Eso significaría que no podemos agregar un campo TerritoryInfo a nuestra clase SavedGame , porque entonces toda la clase SavedGame se volvería no serializable! Ese es un problema: / ![Serialización y deserialización en Java - 2](https://cdn.codegym.cc/images/article/1d7a483d-39da-4c1b-aa2c-a95877c70541/800.jpeg)
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();
}
}
Y aquí está el resultado: SavedGame{territoryInfo=null, resourceInfo=ResourceInfo{info='España tiene 100 de oro, Rusia tiene 80 de oro, Francia tiene 90 de oro'}, diplomacyInfo=DiplomacyInfo{info='Francia está en guerra con Rusia, España ha tomado una posición neutral'}} Dicho esto, obtuvimos una respuesta a la pregunta de qué valor se asignará a un campo transitorio . Se le asigna el valor por defecto. Para los objetos, esto es nulo . Puede leer un excelente capítulo sobre este tema en el libro 'Head-First Java', preste atención :)
GO TO FULL VERSION