Sie sind auf Seite 1von 10

Java ha aadido una interesante faceta al lenguaje denominada serializacin de objetos que permite convertir cualquier objeto cuya

clase implemente el interface Serializable en una secuencia de bytes que pueden ser posteriormente ledos para restaurar el objeto original. Esta caracterstica se mantiene incluso a travs de la red, por lo que podemos crear un objeto en un ordenador que corra bajo Windows 95/98, serializarlo y enviarlo a travs de la red a una estacin de trabajo que corra bajo UNIX donde ser correctamente reconstruido. No tenemos que procuparnos, en absoluto, de las diferentes representaciones de datos en los distintos ordenadores. La serializacin es una caracterstica aadida al lenguaje Java para dar soporte a

La invocacin remota de objetos (RMI) La persistencia

La invocacin remota de objetos permite a los objetos que viven en otros ordenadores comportarse como si vivieran en nuestra propia mquina. La serializacin es necesaria para transportar los argumentos y los valores de retorno. La persistencia, es una caracterstica importante de los JavaBeans. El estado de un componente es configurado durante el diseo. La serializacin nos permite guardar el estado de un componente en disco, abandonar el Entorno Integrado de Desarrollo (IDE) y restaurar el estado de dicho componente cuando se vuelve a correr el IDE.

El interface Serializable
Un objeto se puede serializar si implementa el interface Serializable. Este interface no declara ninguna funcin miembro, se trata de un interface vaco.
import java.io.*; public interface Serializable{ }

Para hacer una clase serializable simplemente ha de implementar el interface Serializable, por ejemplo, a la clase Lista que estudiamos en el captulo Clases y objetos se le aade la implementacin del interface
public class Lista implements java.io.Serializable{ private int[] x; private int n; //otros miembros... }

No tenemos que escribir ningn otro mtodo. El mtodo defaultWriteObject de la clase ObjectOutputStream realiza la serializacin de los objetos de una clase. Este mtodo escribe en el flujo de salida todo lo necesario para reconstruir dichos objetos:

La clase del objeto La firma de la clase (class signature) Los valores de los miembros que no tengan los modificadores static o transient, incluyendo los miembros que se refieren a otros objetos.

El mtodo defaultReadObject de la clase ObjectInputStream realiza la deserializacin de los objetos de una clase. Este mtodo lee el flujo de entrada y reconstruye los objetos de dicha clase.

Lectura/escritura
archivo4: Lista.java ArchivoApp4.java Dos flujos de datos ObjectInputStream y ObjectOutputStream estn especializados en la lectura y escritura de objetos. El comportamiento de estos dos flujos es similar a sus correspondientes que procesan flujos de datos primitivos DataInputStream y DataOutputStream, que hemos visto en la pgina previa Escribir objetos al flujo de salida ObjectOutputStream es muy simple y requiere los siguientes pasos: 1. Creamos un objeto de la clase Lista
Lista lista1= new Lista(new int[]{12, 15, 11, 4, 32});

2. Creamos un fujo de salida a disco, pasndole el nombre del archivo en disco o un objeto de la clase File.
FileOutputStream fileOut=new FileOutputStream("media.obj");

3. El fujo de salida ObjectOutputStream es el que procesa los datos y se ha de vincular a un objeto fileOut de la clase FileOutputStream .
ObjectOutputStream salida=new ObjectOutputStream(fileOut);

o en una sla lnea


ObjectOutputStream salida=new ObjectOutputStream(new FileOutputStream("media.obj"));

4. El mtodo writeObject escribe los objetos al flujo de salida y los guarda en un archivo en disco. Por ejemplo, un string y un objeto de la clase Lista.
salida.writeObject("guardar este string y un objeto\n");

salida.writeObject(lista1);

5. Finalmente, se cierran los flujos


salida.close(); Lista lista1= new Lista(new int[]{12, 15, 11, 4, 32}); ObjectOutputStream salida=new ObjectOutputStream(new FileOutputStream("media.obj")); salida.writeObject("guardar este string y un objeto\n"); salida.writeObject(lista1); salida.close();

El proceso de lectura es paralelo al proceso de escritura, por lo que leer objetos del flujo de entrada ObjectInputStream es muy simple y requiere los siguientes pasos. 1. Creamos un fujo de entrada a disco, pasndole el nombre del archivo en disco o un objeto de la clase File.
FileInputStream fileIn=new FileInputStream("media.obj");

2. El fujo de entrada ObjectInputStream es el que procesa los datos y se ha de vincular a un objeto fileIn de la clase FileInputStream.
ObjectInputStream entrada=new ObjectInputStream(fileIn);

o en una sla lnea


ObjectInputStream entrada=new ObjectInputStream(new FileInputStream("media.obj"));

3. El mtodo readObject lee los objetos del flujo de entrada, en el mismo orden en el que ha sido escritos. Primero un string y luego, un objeto de la clase Lista.
String str=(String)entrada.readObject(); Lista obj1=(Lista)entrada.readObject();

4. Se realizan tareas con dichos objetos, por ejemplo, desde el objeto obj1 de la clase Lista se llama a la funcin miembro valorMedio, para hallar el valor medio del array de datos, o se muestran en la pantalla
System.out.println("Valor medio "+obj1.valorMedio()); System.out.println("-----------------------------"); System.out.println(str+obj1);

5. Finalmente, se cierra los flujos


entrada.close(); ObjectInputStream entrada=new ObjectInputStream(new FileInputStream("media.obj")); String str=(String)entrada.readObject();

Lista obj1=(Lista)entrada.readObject(); System.out.println("Valor medio "+obj1.valorMedio()); System.out.println("-----------------------------"); System.out.println(str+obj1); System.out.println("-----------------------------"); entrada.close();

El modificador transient
archivo6: Cliente.java ArchivoApp6.java Cuando un miembro dato de una clase contiene informacin sensible, hay disponibles varias tcnicas para protegerla. Incluso cuando dicha informacin es privada (el miembro dato tiene el modificador private) una vez que se ha enviado al flujo de salida alguien puede leerla en el archivo en disco o interceptarla en la red. El modo ms simple de proteger la informacin sensible, como una contrasea (password) es la de poner el modificador transient delante del miembro dato que la guarda. La clase Cliente tiene dos miembros dato, el nombre del cliente y la contrasea o password. Redefine la funcin toString miembro de la clase base Object. Esta funcin devolver el nombre del cliente y la contrasea. En el caso de que el miembro password guarde el valor null se imprimir el texto (no disponible). En el cuadro que sigue se muestra el cdigo que define la clase Cliente.
public class Cliente implements java.io.Serializable{ private String nombre; private transient String passWord; public Cliente(String nombre, String pw) { this.nombre=nombre; passWord=pw; } public String toString(){ String texto=(passWord==null) ? "(no disponible)" : passWord; texto+=nombre; return texto; } }

En el cuadro siguiente se muestra los pasos para guardar un objeto de la clase Cliente en el archivo cliente.obj. Posterioremente, se lee el archivo para reconstruir el objeto obj1 de dicha clase.

1. Se crea el objeto cliente de la clase Cliente pasndole el nombre del cliente "Angel" y la contrasea "xyz". 2. Se crea un flujo de salida (objeto salida de la clase ObjectOutputStream) y se asocia con un objeto de la clase FileOutputStream para guardar la informacin en el archivo cliente.obj. 3. Se escribe el objeto cliente en el flujo de salida mediante writeObject. 4. Se cierra el flujo de salida llamando a close.
Cliente cliente=new Cliente("Angel", "xyz"); ObjectOutputStream salida=new ObjectOutputStream(new FileOutputStream("cliente.obj")); salida.writeObject("Datos del cliente\n"); salida.writeObject(cliente); salida.close();

Para reconstruir el objeto obj1 de la clase Cliente se procede del siguiente modo: 1. Se crea un flujo de entrada (objeto entrada de la clase ObjectInputStream) y se asocia con un objeto de la clase FileInputStream para leer la informacin que gurada el archivo cliente.obj. 2. Se lee el objeto cliente en el flujo de salida mediante readObject. 3. Se imprime en la pantalla dicho objeto llamando implcitamente a su funcin miembro toString. 4. Se cierra el flujo de entrada llamando a close.
ObjectInputStream entrada=new ObjectInputStream(new FileInputStream("cliente.obj")); String str=(String)entrada.readObject(); Cliente obj1=(Cliente)entrada.readObject(); System.out.println("------------------------------"); System.out.println(str+obj1); System.out.println("------------------------------"); entrada.close();

La salida del programa es Datos del cliente (no disponible) Angel Lo que nos indica que la informacin sensible guardada en el miembro dato password que tiene por modificador transient no ha sido guardada en el archivo. En la reconstruccin del objeto obj1 con la informacin guardada en el archivo el miembro dato password toma el valor null.

Objetos compuestos

archivo5: Punto.java, Rectangulo.java ArchivoApp5.java Volvemos de nuevo al estudio de la clase Rectangulo que contiene un subobjeto de la clase Punto. A dichas clases se les ha aadido la redefinicin de la funcin toString miembro de la clase base Object (esta redefinicin no es necesaria aunque es ilustrativa para explicar el comportamiento de un objeto compuesto). Como podemos apreciar, ambas clases implementan el interface Serializable. En el cuadro que sigue se muestra parte del cdigo que define la clase Punto.
public class Punto implements java.io.Serializable{ private int x; private int y; //otros miembros... public String toString(){ return new String("("+x+", "+y+")"); } }

La definicin de la clase Rectangulo se muestra en el siguiente cuadro


public class Rectangulo implements java.io.Serializable{ private int ancho ; private int alto ; private Punto origen; //otras funciones miembro... public String toString(){ String texto=origen+" w:"+ancho+" h:"+alto; return texto; } }

Como podemos observar, en la definicin de toString de la clase Rectangulo se hace una llamada implcita a la funcin toString miembro de la clase Punto. La composicin como se ha estudiado permite reutilizar el cdigo existente. Para guardar en un archivo un objeto de la clase Rectangulo hay que seguir los mismos pasos que para guardar un objeto de la clase Lista o de la clase Cliente.
Rectangulo rect=new Rectangulo(new Punto(10,10), 30, 60); ObjectOutputStream salida=new ObjectOutputStream(new FileOutputStream("figura.obj")); salida.writeObject("guardar un objeto compuesto\n"); salida.writeObject(rect);

salida.close();

Para reconstruir un objeto de la clase Rectangulo a partir de los datos guardados en el archivo hay que seguir los mismos pasos que en los dos ejemplos previos.
ObjectInputStream entrada=new ObjectInputStream(new FileInputStream("figura.obj")); String str=(String)entrada.readObject(); Rectangulo obj1=(Rectangulo)entrada.readObject(); System.out.println("------------------------------"); System.out.println(str+obj1); System.out.println("------------------------------"); entrada.close();

En el caso de que nos olvidemos de implementar el interface Serializable en la clase Punto que describe el subobjeto de la clase Rectangulo, se lanza una excepcin, imprimindose en la consola.
java.io.NotSerializableException: archivo5.Punto.

La herencia
archivo8: Figura.java, ArchivoApp8.java En el apartado anterior hemos examinado la composicin, ahora examinemos la herencia. En el captulo de la herencia examinamos una jerarqua formada por una clase base denominada Figura y dos clases derivadas denominadas Circulo y Rectangulo. Como podemos observar en el cuadro adjunto se han hecho dos modificaciones. La clase base Figura implementa el interface Serializable y en la clase Circulo en vez de usar el nmero PI proporcionado por la clase Math, definimos una constante esttica PI con una aproximacin de 4 decimales. De este modo probamos el comportamiento de un miembro esttico en el proceso de serializacin. Para serializar objetos de una jerarqua solamente la clase base tiene que implementar el interface Serializable
public abstract class Figura implements java.io.Serializable{ protected int x; protected int y; public Figura(int x, int y) { this.x=x; this.y=y; } public abstract double area();

} class Circulo extends Figura{ protected double radio; private static final double PI=3.1416; public Circulo(int x, int y, double radio){ super(x,y); this.radio=radio; } public double area(){ return PI*radio*radio; } } class Rectangulo extends Figura{ protected double ancho, alto; public Rectangulo(int x, int y, double ancho, double alto){ super(x,y); this.ancho=ancho; this.alto=alto; } public double area(){ return ancho*alto; } }

Vamos a serializar dos objetos uno de la clase Rectangulo y otro de la clase Circulo, y a continuacin reconstruiremos dichos objetos. Una vez de que dispongamos de los objetos llamaremos a las funciones area para calcular el rea de cada una de las figuras. Para guardar en el archivo figura.obj un objeto fig1 de la clase Rectangulo y otro objeto fig2 de la clase Circulo, se siguen los mismos pasos que hemos estudiado en apartados anteriores
Figura fig1=new Rectangulo(10,15, 30, 60); Figura fig2=new Circulo(12,19, 60); ObjectOutputStream salida=new ObjectOutputStream(new FileOutputStream("figura.obj")); salida.writeObject("guardar un objeto de una clase derivada\n"); salida.writeObject(fig1); salida.writeObject(fig2); salida.close();

Fijarse que fig1 y fig2 son dos referencias de la clase base Figura en la que se guardan objetos de las clases derivadas Rectangulo y Circulo, respectivamente Para leer los datos guardados en el archivo figura.obj y reconstruir dos objetos obj1 y obj2 de las clases Rectangulo y Circulo respectivamente, se procede de forma similar a la estudiada en los apartados previos.
ObjectInputStream entrada=new ObjectInputStream(new

FileInputStream("figura.obj")); String str=(String)entrada.readObject(); Figura obj1=(Figura)entrada.readObject(); Figura obj2=(Figura)entrada.readObject(); System.out.println("------------------------------"); System.out.println(obj1.getClass().getName()+" origen ("+obj1.x+", "+obj1.y+")"+" area="+obj1.area()); System.out.println(obj2.getClass().getName()+" origen ("+obj2.x+", "+obj2.y+")"+" area="+obj2.area()); System.out.println("------------------------------"); entrada.close();

Fijarse que obj1 y obj2 son referencias a la clase base Figura. Sin embargo, cuando obj1 llama a la funcin area nos devuelve (correctamente) el rea del rectngulo y cuando, obj2 llama a la funcin area devuelve el rea del crculo. Fijarse tambin que aunque PI es un miembro esttico de la clase Circulo, se reconstruye el objeto obj2 con el valor del miembro esttico con el que se calcula el rea del crculo

Serializacin personalizada
El proceso de serializacin proporcionado por el lenguaje Java es suficiente para la mayor parte de las clases, ahora bien, se puede personalizar para aquellos casos especficos. Para personalizar la serializacin, es necesario definir dos funciones miembros writeObject y readObject. El primero, controla que informacin es enviada al flujo de salida. La segunda, lee la informacin escrita por writeObject . La definicin de writeObject ha de ser la siguiente
private void writeObject (ObjectOutputStream s) throws IOException{ s.defaultWriteObject(); //...cdigo para escribir datos }

La funcin readObject ha de leer todo lo que se ha escrito con writeObject en el mismo orden en el que se ha escrito. Adems, puede realizar otras tareas necesarias para actualizar el estado del objeto.
private void readObject (ObjectInputStream s) throws IOException{ s.defaultReadObject(); //...cdigo para leer datos //... //actualizacin del estado del objeto, si es necesario }

Para un control explcito del proceso de serializacin la clase ha de implementar el interface Externalizable. La clase es responsable de escribir y de leer su contenido, y ha de estar coordinada con sus calses base para hacer esto. La definicin del interface Externalizable es la siguiente
packege java.io; public interface Externalizable extends Serializable{ public void writeExternal(ObjectOutput out) throws IOException; public void readExternal(ObjectOutput in) throws IOException, java.lang.ClassNotFoundException;; }