Cuando una clase implementa una interface, obliga a sobrescribir los métodos de esa interface. En Java tenemos muchas interfaces para hacer que nuestras clases tengan una forma común. La interfaz Serializable sirve para poder indicar que un objeto puede ser serializado; guardarlo en fichero como un objeto tal cual.
https://docs.oracle.com/javase/8/docs/api/java/io/Serializable.html
Java ofrece un mecanismo llamado serialización de objetos, mediante el cual un objeto puede ser representado por una secuencia de bytes que incluye tanto los datos del objeto, como el tipo del objeto y los tipos de los datos que contiene ese objeto.
Gracias a esto podemos guardar el estado de un objeto en fichero o enviarlo por la red, y permite posteriormente leer esos bytes para reconstruir el objeto y su estado en la memoria de nuevo. Además puede ser serializado en una máquina y deserializado en otra máquina distinta.
Para que un objeto sea serializable debe implementar la interface Serializable. Esta interface es algo peculiar ya que no tiene métodos; simplemente sirve para “marcar” las clases que permiten ser serializadas. Basta con implementarla y ya podremos serializar sus objetos.
Hay que tener en cuenta que, para poder traducir un objeto a bytes de forma correcta, todos los objetos que lo componen deben ser serializables.
Los miembros estáticos de una clase, no son parte del estado de un objeto (sino de la clase) y no permiten ser serializados.
La mayoría de las clases de la API de Java implementan esta interface, pero nuestros objetos deben implementarla también si queremos serializarlos.
public class Coche implements Serializable{ private String matricula; private Persona conductor; //El atributo conductor también debe ser serializable, String ya lo es }
Si dentro de la clase Coche hay atributos de otras clases, deben ser también serializables. Como decimos, con la mayoría de objetos de Java no hay problema, pero sí con los nuestros propios.
En el caso anterior el atributo conductor de tipo Persona debe ser también serializable, por lo que la clase Persona debe implementar también dicha interface.
Cuando serializamos objetos para deserializarlos posteriormente debemos tener en cuenta un aspecto. Si la clase que queremos pasar es Coche, lo normal es que en ambos programas (el que serializa y el que deserializa), tengan su propia copia del fichero Coche.class. Pero también es posible que en distintas versiones de nuestro programa la clase Coche no sea igual, de forma que es posible que un lado tenga una versión más antigua que en el otro lado. Si sucede esto, la reconstrucción de la clase en el lado que recibe sera imposible.
Para evitar este problema, se aconseja que la clase Coche tenga un atributo privado de esta forma:
private static final long serialVersionUID = 8799656478674716638L;
…de forma que el numerito que ponemos al final debe ser distinto para cada versión de compilado que tengamos. De esta forma, Java es capaz de detectar rápidamente que las versiones de Coche.class
en ambos lados son distintas o iguales.
Algunos entornos de desarrollo, como eclipse, dan un warning si una clase que implementa Serializable (o hereda de una clase que a su vez implementa Serializable) no tiene definido este campo. También nos ayuda a generarlo automáticamente si se lo pedimos. En eclipse basta con hacer click con el ratón sobre el símbolo de warning para que nos indique las posibles soluciones al warning. Una de ellas genera el número automáticamente.
Es posible utilizar los mecanismos de serialización disponibles para serializar un objeto guardándolo en un fichero.
Tenemos un flujo de datos concreto para ficheros: FileOutputStream. El constructor de esta clase puede crear un flujo hacia un fichero a partir de un objeto File o de un String con la ruta.
El flujo de datos ObjectOutputStream está especializado en la escritura de objetos. Necesita que le indiquemos que su flujo de escritura será hacia un fichero, a partir de un objeto FileOutputStream.
Esto se puede realizar en una sola línea:
ObjectOutputStream flujoObjetos= new ObjectOutputStream(new FileOutputStream(new File("fichero.bin"));
O en líneas diferentes:
File fichero = new File("fichero.bin"); FileOutputStream flujoFichero = new FileOutputStream(fichero); ObjectOutputStream serializador = new ObjectOutputStream(flujoFichero);
El método writeObject de la clase ObjectOutputStream escribe los objetos al flujo de salida y los guarda en un fichero de disco.
Coche miCoche1 = new Coche(); Coche miCoche2 = new Coche(); // Escribir objetos en el fichero serializador.writeObject("Guardo 2 objetos Coche"); // Puedo almacenar objetos String serializador.writeObject(miCoche1); // u objetos tipo Coche serializador.writeObject(miCoche2); . . . serializador.close(); // Finalmente se cierran los flujos de salida, cerrando el más externo.
Puedo serializar estructuras completas, como arraylists o arrays, o cualquier clase que contenga cualquier estructura, sin necesidad de serializar por separado sus elementos.
El proceso de lectura desde fichero es paralelo al de escritura. Debo usar los flujos anteriores, pero en este caso deben ser de lectura:
File fichero = new File("fichero.bin"); FileInputStream flujoFichero = new FileInputStream(fichero); ObjectInputStream deserializador = new ObjectInputStream(flujoFichero); // Leer el objeto del fichero (en el mismo orden !!) String cadena = (String) flujoObjetos.readObject(); //Leo el objeto tipo String Coche cocheLeido1 = (Coche) deserializador.readObject(); //Leo los objetos tipo Coche Coche cocheLeido2 = (Coche) deserializador.readObject(); deserializador.close(); //Finalmente se cierran los flujos de entrada
Como hemos visto en otras ocasiones las clases de trabajo con ficheros suelen lanzar excepciones que debemos de controlar con la estructura try-catch-finally.
En el caso de lectura de listas de objetos, nuestro programa no puede asegurar de ninguna forma que los bytes que lee del fichero son los tipos de datos que nosotros le indicamos:
ArrayList<Coche> lista = (ArrayList<Coche>) deserializador.readObject();
Aunque nosotros sepamos que se lee lo correcto no hay forma de que Java “lo sepa”. Podríamos cambiar el fichero, y no leer el objeto que esperamos leer.
Por lo tanto, Eclipse nos indica un warning al hacer un casting de bytes leidos desde fichero. La manera más sencilla de eliminarlo es añadiendo la siguiente linea al comienzo del método que genera dicha advertencia:
@SuppressWarnings("unchecked")
© 2024 Fernando Valdeón