Você está na página 1de 15

Serializacin de objetos en Java

DESCRIPCIN Este artculo describe la serializacin de objetos en Java, de forma aplicada, mediante ejemplos. Introduccin La serializacin de un objeto consiste en obtener una secuencia de bytes que represente el estado de dicho objeto. Esta secuencia puede utilizarse de varias maneras (puede enviarse a travs de la red, guardarse en un fichero para su uso posterior, utilizarse para recomponer el objeto original, etc.). ESTADO DE UN OBJETO El estado de un objeto viene dado, bsicamente, por el estado de sus campos. As, serializar un objeto consiste, bsicamente, en guardar el estado de sus campos. Si el objeto a serializar tiene campos que a su vez son objetos, habr que serializarlos primero. ste es un proceso recursivo que implica la serializacin de todo un grafo (en realidad, un rbol) de objetos. Adems, tambin se almacena informacin relativa a dicho rbol, para poder llevar a cabo la reconstruccin del objeto serializado. En ocasiones puede interesar que un atributo concreto de un objeto no sea serializado. Esto se puede conseguir utilizando el modificador transient, que informa a la JVM de que no nos interesa mantener el valor de ese atributo para serializarlo o hacerlo persistente. OBJETOS SERIALIZABLES. INTERFAZ SERIALIZABLE Un objeto serializable es un objeto que se puede convertir en una secuencia de bytes. Para que un objeto sea serializable, debe implementar la interfaz java.io.Serializable. Esta interfaz no define ningn mtodo. Simplemente se usa para 'marcar' aquellas clases cuyas instancias pueden ser convertidas a secuencias de bytes (y posteriormente reconstrudas). Objetos tan comunes como String, Vector o ArrayList implementan Serializable, de modo que pueden ser serializados y reconstrudos ms tarde. Para serializar un objeto no hay ms que declarar el objeto como serializable: public class MiClase implements java.io.Serializable El sistema de ejecucin de Java se encarga de hacer la serializacin de forma automtica.

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
Lista.java
package archivo4; public class Lista implements java.io.Serializable{ private int[] x; //array de datos private int n; //dimensin public Lista(int[] x) { this.x=x; n=x.length; ordenar(); } public double valorMedio(){ int suma=0; for(int i=0; i<n; i++){ suma+=x[i]; } return (double)suma/n; } public int valorMayor(){ return x[n-1]; } public int valorMenor(){ return x[0]; } private void ordenar(){ int aux; for(int i=0; i<n-1; i++){ for(int j=i+1; j<n; j++){ if(x[i]>x[j]){ aux=x[j]; x[j]=x[i]; x[i]=aux; } } } } public String toString(){ String texto=""; for(int i=0; i<n; i++){ texto+="\t"+x[i]; } return texto; } }

ArchivoApp4.java
package archivo4; import java.io.*; import java.util.*; public class ArchivoApp4 { public static void main(String[] args) { Lista lista1= new Lista(new int[]{12, 15, 11, 4, 32}); try { ObjectOutputStream salida=new ObjectOutputStream(new FileOutputStream("media.obj")); salida.writeObject("guardar este string y un objeto\n"); salida.writeObject(lista1); salida.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(); //se puede fundir en una catch Exception }catch (IOException ex) { System.out.println(ex); }catch (ClassNotFoundException ex) { System.out.println(ex); } try { //espera la pulsacin de una tecla y luego RETORNO System.in.read(); }catch (Exception e) { } } }

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
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
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
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;; }
A continuacin veremos un ejemplo que consistir en:

Una clase Empleado con los atributos legajo, apellido y nombre, y sus respectivos getters. Un mtodo serializar, que recibir como parmetro el nombre del archivo donde escribir el archivo. Un mtodo hidratar, que recibir como parmetro el nombre del archivo de donde recuperar el objeto. Una clase Test con el mtodo main, que se encargar de crear el objeto empleado y serializarlo, y luego de recuperarlo (hidratarlo)

/** * Empleado.java * */ package serializacion;


import import import import import import java.io.FileInputStream; java.io.FileOutputStream; java.io.IOException; java.io.ObjectInputStream; java.io.ObjectOutputStream; java.io.Serializable;

/** * @author Luciano * @version 1.0 * */ @SuppressWarnings("serial") public class Empleado implements Serializable { /** * Atributos de clase */ int legajo; String apellido; String nombre; /** * Constructor * * @param legajo Legajo del empleado

* @param apellido Apellido del empleado * @param nombre Nombre del empleado */ public Empleado(int legajo, String apellido, String nombre) { super(); this.legajo = legajo; this.apellido = apellido; this.nombre = nombre; } /** * Constructor * */ public Empleado() { super(); } /** * Escribe a archivo el objeto Empleado * * @param archivo Nombre de archivo * @throws IOException Excepcin de entrada/salida */ public void serializar (String archivo) throws IOException { ObjectOutputStream salida = new ObjectOutputStream(new FileOutputStream(archivo)); salida.writeObject(this); } /** * * @param archivo Nombre de archivo * @return Objeto hidratado * @throws IOException Excepcin de entrada/salida * @throws ClassNotFoundException Escepcin de clase no encontrada */ public Empleado hidratar (String archivo) throws IOException, ClassNotFoundException { ObjectInputStream entrada = new ObjectInputStream(new FileInputStream(archivo)); return (Empleado) entrada.readObject(); } /** * Getter de Apellido de Empleado * @return Apellido del empleado */ public String getApellido() { return apellido; }

/** * Getter de Legajo de Empleado * @return Legajo del empleado */ public int getLegajo() { return legajo; } /** * Getter de Nombre de Empleado * @return Nombre del empleado */ public String getNombre() { return nombre; } }

/** * Test.java * */ package serializacion;


import java.io.IOException; public class Test { /** * Main * * @param args */ public static void main(String[] args) { /* Creacin y serializacin del objeto Empleado */ Empleado s = new Empleado(122,"Fernandez","Jorge"); try { s.serializar("empleado_serializado"); } catch (IOException e1) { System.err.print(e1.getMessage()); } /* Creacin de un objeto Empleado vaco e hidratacin */ Empleado h = new Empleado(); try { h = h.hidratar("empleado_serializado"); System.out.println("Legajo....: " + h.getLegajo()); System.out.println("Apellido..: " + h.getApellido());

System.out.println("Nombre....: " + h.getNombre()); } catch (IOException e1) { System.err.print(e1.getMessage()); } catch (ClassNotFoundException e1) { System.err.print(e1.getMessage()); } } }

Você também pode gostar