viernes, 16 de marzo de 2012

Serialización de objetos en Java I

votar
   La serialización consiste en convertir un objeto en una secuencia de bytes para guardarlo en un archivo o enviarlo por la red, y luego reconstruirlo, con los valores que tenía al ser serializado, para su posterior utilización. La serialización es muy utilizada en las bases de datos relacionales, pero tiene también otras aplicaciones.

   En Java, esta capacidad de serialización, es decir, de guardar información sobre un objeto para luego recuperarla, se llama persistencia.

   Para que un objeto sea serializable basta con que la clase a la que pertenezca, o una superclase de ésta, implemente la interfaz Serializable o su subinterfaz Externalizable, ambas en el paquete java.io.

  Muchas clases en la Java API implementan Serializable: clases de utilidad, como java.util.Date, o todas las clases de componentes de Swing GUI; pero por si acaso, asegúrate siempre, porque si intentas serializar un objeto de una clase que no implementa la interfaz Serializable, se producirá una NotSerializableException al ejecutar el programa.

    Implementar Serializable es muy sencillo, porque no tiene métodos. En realidad actúa como un marcador que indica que los objetos de una clase (o de sus subclases) son serializables, de esta manera: 

     class MiClase implements Serializable

   Ahora bien, para utilizar correctamente Serializable, sin liarla, hay que tener en cuenta algunos detalles.

   Primero, lo que estamos serializando son objetos y sus campos, así que las variables marcadas como static, es decir, que pertenecen a la clase y no al objeto, no pueden ser serializadas.

   Segundo, supongamos que queremos serializar un objeto que contiene una referencia a una instancia de una clase que no es serializable. Esto produciría la ya conocida NotSerializableException. Para evitarlo, debemos marcar esa instancia como transient.
   Todos los campos marcados como transient serán ignorados por la JVM en el proceso de serialización.

   Veamos todo esto con un ejemplo:

   public class MiClase implements Serializable{
      private int variable1;
      private static double variable2;
      private transient OtraClase oc = new OtraClase();
   }
   class OtraClase{} //OtraClase no es serializable

   Cuando un objeto de la clase MiClase sea serializado, sólo su variable "variable1" será serializada. La variable "variable2" no, porque es static y por tanto pertenece a la clase y no al objeto, y la variable oc tampoco porque está marcada transient. Si no añadiéramos el modificador transient tendríamos una NotSerializableException, pero también podemos utilizarlo cuando no nos interese serializar algo.

   Pasemos a ver ahora un ejemplo práctico de serialización y recuperación de objetos. Vamos a escribir unos datos en un archivo, serializarlos y recuperarlos después.

import java.io.*;
public class TestAgenda{
   public static void main(String[] args){
      Agenda a1 = new Agenda("Ana", "Martínez", "Fernández");
      Agenda a2 = new Agenda("Ernesto", "García", "Pérez");
      try{
         FileOutputStream fs = new FileOutputStream("agenda.ser");//Creamos el archivo
         ObjectOutputStream os = new ObjectOutputStream(fs);//Esta clase tiene el método writeObject() que necesitamos
         os.writeObject(a1);//El método writeObject() serializa el objeto y lo escribe en el archivo
         os.writeObject(a2);
         os.close();//Hay que cerrar siempre el archivo
      }catch(FileNotFoundException e){
         e.printStackTrace();
      }catch(IOException e){
         e.printStackTrace();
      }
      try{
         FileInputStream fis = new FileInputStream("agenda.ser");
         ObjectInputStream ois = new ObjectInputStream(fis);
         a1 = (Agenda) ois.readObject();//El método readObject() recupera el objeto
         a2 = (Agenda) ois.readObject();
         ois.close();
      }catch(FileNotFoundException e){
         e.printStackTrace();
      }catch(IOException e){
         e.printStackTrace();
      }catch(ClassNotFoundException e){
         e.printStackTrace();
      }
      
      System.out.println(a1);
      System.out.println(a2);
   }
}

class Agenda implements Serializable{
   private String nombre;
   private String p_Apellido;
   private String s_Apellido;
    /*getters y setters*/
   public String getNombre(){
      return nombre;
   }
   public void setNombre(String nombre){
      this.nombre = nombre;
   }
   public String getP_Apellido(){
      return p_Apellido;
   }
   public void setP_Apellido(String p_Apellido){
      this.p_Apellido = p_Apellido;
   }
   public String getS_Apellido(){
      return s_Apellido;
   }
   public void setS_Apellido(String s_Apellido){
      this.s_Apellido = s_Apellido;
   }
   public Agenda(String nombre, String p_Apellido, String s_Apellido){
      super();
      this.nombre = nombre;
      this.p_Apellido = p_Apellido;
      this.s_Apellido = s_Apellido;
   }
   public String toString(){
         return( getNombre() + " " + getP_Apellido() + " " + getS_Apellido());
      }
}
   
   Lo visto hasta ahora es la serialización por defecto. En la próxima entrada veremos como implementarla según nuestras necesidades. : )

12 comentarios:

  1. Hola Sonia. Sigo tu blog desde hace algún tiempo y he decir que me está ayudando enormemente para aprobar los exámenes! Mil gracias! Ahora mismo me estoy introduciendo en las GUIs y me gustaría encontrar alguna introducción aquí en tu blog; soy un poco patoso y no la encuentro, jaja, o si no es así, alguna página que tú me recomiendes para ello! Muchisimas gracias!

    ResponderEliminar
    Respuestas
    1. Hola, Anónimo. Me ha alegrado el día saber que mi blog te está ayudando en tus estudios.
      Es cierto que apenas he hablado sobre las GUI en el mismo, apenas una entrada sobre redimensionamiento de imágenes en componentes de JFrame (puedes encontrarla en la lista de entrada a la derecha) Prometo que me pondré a ello en cuanto pueda, aunque últimamente entre el trabajo y unos cursos de edx que estoy siguiendo tengo el blog un poco abandonado.
      Mientras tanto, hay algunos buenos tutoriales en internet. ¿Cómo andas de inglés? Échale un ojo a este enlace: http://cs.nyu.edu/~yap/classes/visual/03s/lect/l7/
      y por supuesto a los tutoriales de Oracle: http://docs.oracle.com/javase/tutorial/uiswing/
      Por si acaso el inglés no es lo tuyo, he estado echando un vistazo a páginas en castellano. No he encontrado muchas, pero quizás te pueda ser útil este tutorial de Youtube: http://www.youtube.com/watch?v=PBM1xOvC33w
      o este otro tutorial que ya utiliza Java 7: http://tutorialjava7.wordpress.com/2010/10/19/tutorial-java-swing-introduccion/
      o también este: http://www.geekytheory.com/tutorial-14-java-swing-interfaces-graficas/
      o este otro, aunque ya tiene unos cuantos añitos: http://www.adictosaltrabajo.com/tutoriales/tutoriales.php?pagina=swing
      Ya te digo que sólo he echado un vistazo y no sé qué tal estarán, pero espero que te sirvan, al menos de momento.
      Gracias por leerme y no olvides contarme qué tal te ha ido cuando acabes el curso. :-)

      Eliminar
  2. Buenas Sonia,

    En primer lugar agradecerte esta info pues me ha orientado un poco a la hora de afrontar un ejercicio que tenia que resolver. Aun asi, sigo medio pez.

    El caso es que si con un menu, en una agenda, selecciono la opcion de ingresar un usuario al fichero y a continuacion desarrollo lo mismo. Machaco en el fichero los datos anteriores. Para ello he visto que he de implementar un "true" aqui:

    ObjectOutputStream os = new ObjectOutputStream(fs,true);

    El problema es que, a pesar de que ahora si que me introduce mas lineas en el documento, cuando intento leerlo, me muestra el primer objeto y el segundo me planta un error.

    Por lo que he leido, es que cada vez que abrimos el fichero, se crea una especie de cabecera y al llegar a ella cuando leemos, nos planta el error.

    Sabes algo acerca de esto? Podrias orientarme un poco? Muchisimas gracias de antemano.

    ResponderEliminar
    Respuestas
    1. Hola, Anónimo.
      El problema con las cabeceras surge cuando no se crean todos los datos en la misma sesión, es decir, que hoy creamos unos,cerramos, y al día siguiente o a los dos días introducimos unos nuevos (el "true" se utiliza para añadir nuevos datos al final del fichero). Cada vez que haces eso se crea un header nuevo y claro, luego al leerlo el programa se da cuenta de que ese header no es un objeto de los que esperaba y te da StreamCorruptedException (o eso espero que sea) La mejor solución es sobreescribir el método ObjectOutputStream.writeStreamHeader(), que se utiliza para que las subclases añadan sus propias cabeceras, dejándolo en blanco para que no haga nada. Te dejo el enlace a la documentación de Oracle sobre los métodos de ObjectOutputStream: http://docs.oracle.com/javase/1.5.0/docs/api/java/io/ObjectOutputStream.html
      También te dejo un enlace a StackOverFlow sobre cómo sobreescribirlo por si mi solución no te convence: http://stackoverflow.com/questions/1085812/how-to-override-objectoutputstream-writestreamheader
      y su traducción al castellano (más o menos) por si el inglés te da problemas: http://es.softuses.com/147810
      ¿Supongo que para leer los datos utilizarás un while loop, no?
      También, por si te sirve, he encontrado de casualidad un enlace a un ejemplo de una agenda.
      No tengo ni idea de qué año es, ni de quién es Mariano (me da a mí que no es el presidente Rajoy) pero me hizo mucha gracia, así que te la incluyo: http://agendamariano.googlecode.com/svn-history/r2/trunk/src/Persona.java
      También te puede ser útil la segunda parte de esta entrada, si aún no la has leído.
      En fin, espero que esto te ayude en tu ejercicio.
      Un saludo.

      Eliminar
    2. Buenas Sonia,

      Soy el Anonimo de antes jaja, me llamo Ivan.

      En primer lugar, mil gracias por responder y por la prontitud de tu respuesta.

      En segundo lugar, gracias por todos los enlaces, les echare un ojo a todos y vere que termino haciendo.

      Respecto a tu pregunta de si leo con un while, si. Exactamente asi:

      FileInputStream file1 = new FileInputStream("D:/clientes.dat");
      ObjectInputStream ois = new ObjectInputStream(file1);

      Object aux = ois.readObject();

      while(aux!=null){
      if (aux instanceof MetodosClientes){

      System.out.println(aux);

      }

      aux = ois.readObject();
      }
      ois.close();


      Nuevamente gracias por todo. Me pongo a ello a ver que tal se da.

      Eliminar
  3. como hacer mandar un .class a un un cliente utilizando la serializacion

    ResponderEliminar
    Respuestas
    1. Hola, Anónimo:
      Para serializar una clase, recuerda marcar como transient los campos que no sean serializables y luego puedes hacer algo así
      class MiClase{}
      File file = new File ("miArchivo.ser");
      ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream(file));
      os.writeObject(MiClase.class);
      Pero para que tu cliente pueda deserializarlo, tiene que tener acceso a la definición de la clase, bien teniendo una copia, bien pudiendo acceder a la misma.
      Una vez que has serializado la clase puedes utilizar la API RMI para enviarla. Como ese tema es muy amplio para responderlo aquí, te dejo un enlace que espero te ayude:
      http://www.chuidiang.com/java/rmi/rmi_parametros.php
      Si no sabes nada sobre RMI, ve a la página previa de ese mismo enlace.
      Un saludo.

      Eliminar
  4. Hola Sonia, eres bonita :)

    ResponderEliminar
  5. Hola Sonia nomas dime mas o menos para que sirve la serializacion o cual es su aplicacion

    ResponderEliminar
    Respuestas
    1. Hola, Anónimo:
      Como ya hemos visto, la serialización consiste en convertir un objeto y sus atributos en una serie de bytes, bien para almacenarlos en una base de datos o en un archivo, bien para poder enviarlos a través de una red.
      Un ejemplo práctico de su utilización que se me ocurre ahora mismo es cuando en un juego quieres guardar una partida en el punto exacto en el que estás. Se serializan entonces los objetos necesarios para ello, y al reanudarla se "deserializan", recuperándolos tal y como los guardastes.
      Pero cualquier otro ejemplo en el que se necesiten guardar datos o enviarlos es válido.
      ¡Espero haberte ayudado! Un saludo.

      Eliminar
  6. Hola Sonia.
    Hay casos en los que guardo en un archivo de texto los datos de por ejemplo una entidad cliente y no tuve la necesidad de colocar en la clase Cliente implements Serializable. Me podrías dar un brebe alcance porque se guardo a pesar que no le puse implements Serializable si se supone q sin colocar eso no debería de guardar. Gracias.

    ResponderEliminar
  7. hola hola Sonia soy nueva por aquí y tengo un problema debo hacer un programa que cuente todos los caracteres de un libro y no se como hacerlo (me podría ayudar por favor).

    ResponderEliminar