Documentos de Académico
Documentos de Profesional
Documentos de Cultura
En esta entrada vamos a ver lo referente a la Serialización en Java, que es, para que se usa y como podemos
aplicarla.
Imaginemos que queremos guardar el estado de uno o mas objetos. Si java no tuviera la serialización (como
las primeras versiones no tenían), tendríamos que haber usado algunas de las clases de I/O para escribir el
estado de las variables de instancia de todos los objetos que quisieramos guardar. La peor parte de esto sería
reconstruir todos estos objetos nuevos que serían virtualmente identicos a los objetos que estabamos
intentando guardar. Necesitaríamos nuestro propio protocolo para la manera en la cual lo escribimos y en la
que restauramos el estado de cada objeto, o podríamos establecer variables con valores incorrectos. Por
ejemplo, imaginemos ue hemos guardado un objeto que tiene unas variables de instancia para el ancho y la
altura de algo. Cuando los guardamos el estado del objeto, podríamos escribir la altura y el ancho como 2 int
en un fichero, pero el orden en el que lo hemos escrito es crucial. Sería muy facil recrear el objeto pero a la
vez sería muy facil equivocarse con las variables, y asignar el ancho a la altura o viceversa.La serialización
nos permite decir “Guarda este objeto y todas sus variables de instancia.” Actualmente es algo mas
interesante que esto, porque podemos agregar, “… a no ser que una variable sea marcada como transient, lo
que significa, que no se incluye el valor de las variables marcadas como transient como parte del estado del
objeto serializado”.
Trabajando con ObjectOutputStream y ObjectInputStream
La magia de la serializacion ocurre con solo 2 métodos: uno para serializar objetos y escribirlos en un stream,
el package java.io, y como vimos con anterioridad en la anterior sección, esto significa que las envolveremos
1. Hemos declarado una clase Cat la cual implementa la interface Serializable. Es una
interface que marca, no tiene métodos que implementar.
2. Hemos creado un objeto Cat, el cual como sabemos es serializable.
3. Hemos serializado el objeto Cat cuya variable de referencia es c invocando el método
writeObject(). Se requiere una preparación anterior antes de que podamos serializar nuestro Cat. Primero
tenemos que poner todo el código relacionado con la operación I/O en un bloque try/catch. A continuación
tenemos que crear un FileOutputStream para escribir el objeto. Despues hemos envuelto el objeto
FileOutputStream en un ObjectOutputStream, el cual es la clase que tiene el método mágico de serialización
que necesitamos. Recordemos que la invocación de writeObject() realiza 2 tareaas: serializa el objeto, y
entonces escribe el objeto serializado en un fichero.
4. De-serializamos el objeto Cat invocando el método readObject(). Este método retorna un
objeto, por lo que tenemos que hacer un cast al objeto que hemos deserializado a Cat. De nuevo, tenemos
que hacer todo lo relacionado con las operaciones I/O, bloque try/catch, etc.
Este es un ejemplo muy básico para comprobar la serialización en acción, y apenas tiene dificultad. En las
siguientes secciones vamos a ver ejemplos mas complejos y que nos pueden dar problemas relacionados con
la serialización.
¿Qué significa realmente guardar un objeto?. Si las vriables de instancia son todas de tipos primitivos, es muy
fácil. Pero ¿Qué pasa si las variables de instancia son referencias a otros objetos? ¿Qué se salva?.
Claramente en Java no tendría sentido salvar el valor actual de las variables de referencia, porque son valores
de una referencia en Java que tiene sentido solo en el contexto de una singular JVM, incluso ejecutandose en
el mismo ordenador en el cual el objeto fuera originalmente serializado, la referencía no tendría utilidad. Pero
¿Qué pasa con el objeto al cual la referencia se refiere? Veamos esta clase:
1
class Dog {
2
private Collar theCollar;
3
private int dogSize;
4
public Dog(Collar, int size) {
5
theCollar = collar;
6
dogSize = size;
7
}
8 public Collar getCollar() { return theCollar; }
9 }
10 class Collar {
11 private int collarSeize;
12 public Collar (int size) { collarSize = size; }
13 public int getCollarSize() { return collarSize; }
14 }
15 [/sourcecode[]
16 <p>Ahora hacemos un objeto Dog, pero primero hacemos un collar para el perro:</p>
17
18 Collar c = new Collar(3);
19
1
import java.io.*;
2
public class SerializeDog {
3
public static void main(String[] args){
4
Collar c = new Collar(3);
5
Dog d = new Dog(c, 8);
6 try{
7 FileOutputStream fs = new FileOutputStream("testSer.ser");
8 ObjectOutputStream os = new ObjectOutputStream(fs);
9 os.writeObject(d);
10 os.close();
11 }catch (Exception ex){ ex.printStackTrace(); }
12 }
13 }
14
Pero cuando ejecutamos este código tendremos una excepción en tiempo de ejecución que se parecería a lo
siguiente:
1 java.io.NotSerializableException: Collar
2
¿Qué hemos olvidado? La clase Collar debe tambien ser Serializable. Si modificamos la clase Collar y la
1
2 import java.io.*;
3 public class SerializeDog{
4 public static void main (String[] args){
5 Collar c = new Collar(3);
6 Dog d = new Dog(c, 5);
System.out.println("before: collar size is " + d.getCollar().getCollarSize());
7
try{
8
FileOutputStream fs = new FileOutputStrem("testSer.ser");
9
ObjectOutputStream os = new ObjectOutputStream(fs);
10
os.writeObject(d);
11
os.close();
12
} catch (Exception e) {e.printStackTrace(); }
13
14
try {
15
FileInputStream fis = new FileInputStream("testSer.ser");
16
ObjectInputStream ois = new ObjectInputStream(ois);
17
d = (Dog)ois.readObject();
18
ois.close();
19
} catch(Exception e) { e.printStackTrace(); }
20
21
System.out.println("after: collar size is " + d.getCollar().getCollarSize());
22
}
23
}
24
class Dog implements Serializable {
25
private Collar theCollar;
26
private int dogSize;
27 public Dog (Collar collar, int size){
28 theCollar = collar;
29 dogSize = size;
30 }
31 public Collar getCollar(){
32 return theCollar;
33 }
34 }
35 class Collar implements Serializable {
36 private int collarSize;
37 public Collar(int size){
38 collarSize = size;
39 }
40 public int getCollarSize() {
41 return collarSize;
42 }
43 }
44
Ahora tenemos un Dog serializable, con un objeto Collar no serializable, pero el Dog tiene Collar como
Consideramos el problema: tenemos un objeto Dog que queremos salvar. El Dog tiene Collar, y el estado de
Collar que debería tambien ser guardado es parte del estado de Dog. Pero…el Collar no es serializable, por lo
que lo hemos marcado como transient. Esto significa que cuando Dog es deserializado, viene con el un
Collar cuyo valor es null. ¿ue podemos hacer para estar eguros de que Dog es deserializado y obtiene un
Collar que coincida con el que Dog tenía cuando fué salvado?. La serialización en Java tiene unos mecanimos
especiales para esto, un set de métodos privados que podemos implementar en nuestra clase, y si están
presentes, serán invocados automáticamente durante la serialización y la deserialización. Es como si estos
métodos estuvieran definidos en la interface Serializable, excepto que no lo están. Son parte de una llamada
especial junto con el sistema de serialización que básicamente dice, “Si tienes un par de métodos que se
llaman igual, estos métodos serán llamados durante el proceso de la serialización/deserialización”.
Estos métodos nos permiten un paso en la mitad de la serialización y deserialización. Por esto es perfecto
para permitir solventar el problema de Dog/Collar: cuando un Dog está siendo salvado, podemos entrar en el
medio de la serialización y decir, “Me gustaría guardar el estado de la variable Collar (un int) en el stream
cuando el Dog sea serializado”. Hemos añadido manualmente el estado de Collar a la representación de Dog
serializada, incluso aunque el Collar por sí mismo no sea salvado.
Por suuesto, necesitarás restaurar el Collar durante la deserialización entrando en la mitad del proceso y
diciendo, “Leeré un int extra que salvé en el stream de Dog, y lo uso para crear un nuevo Collar, y entonces
se lo asigno al nuevo Collar del Dog que se está deserializando”. Los 2 métodos especiales que definimos
tienen que tener el mismo nombre, EXACTAMENTE el siguiente:
1 private void writeObject(ObjectOutputStream os){
2 // Cödigo para guardar las variables de collar
3
}
4
5 private void readObject(ObjectInputStream is){
6 // El código para leer el estado de Collar, crear un nuevo Collar
7 // y asignarlo a Dog
8 }
9
Si, estamos escribiendo métodos con el mismo nombre que los que hemos estado llamando. ¿Dónde van
1
2 class Dog implements Serializable{
3 transient private collar theCollar; // No podemos seralizar esto
private int dogSize;
4
public Dog(Collar collar, int size){
5
theCollar = collar;
6
dogSize = size;
7
}
8
public Collar getCollar(){
9
return theCollar;
10
}
11
private void writeObject(ObjectOutputStream os){
12 // throws IOException{
13 try{
14 os.defaultWriteObject();
15 os.writeInt(theCollar.getCollarSize());
16 }catch (Exception e) { e.printStackTrace(); }
17 }
18 private void readObject(ObjectInputStream is){
19 // throws IOException, ClassNotFoundException
20 try{
21 is.defaultReadObject();
22 theCollar = new Collar(is.readInt());
23 } catch (Exception e) { e.printStackTrace(); }
24 }
25 }
26
En nuestro escenario hemos agregado que, por alguna razón del mundo, no podemos serializar el objeto
Collar, pero queremos serializar Dog. Para hacer esto vamos a implementar los métodos writeObject() y
readObject(). Implementando estos métodos estamos diciendole al compilador: “Si algo invoca a writeObject()
o readObject() que concierne con el objeto Dog, usa esta parte del código de para leer y escribir”-
1. Como todos los métodos relacionados con procesos de I/O, writeObject puede lanzar