sábado, 24 de marzo de 2012

Serialización de objetos en Java II

votar
   En la entrada anterior vimos la forma de serializar objetos por defecto, es decir, utilizando los métodos writeObject() y readObject()  tal cual.

   Pero, ¿qué ocurre si los métodos así implementados no cubren nuestras necesidades? Por ejemplo, supongamos que tenemos un campo declarado transient pero que aún así necesitamos serializar, o unos datos que sólo queremos en el servidor receptor, y no en el emisor, como por ejemplo la actualización automática de la hora. 

   Para esto bastará con que escribamos nuestros propios métodos writeObject() y readObject() así:

   private void writeObject(ObjectOuputStream os){
      //aquí el código que necesitemos
   }
   private void readObject(ObjectInputStream is){
      //aquí el código que necesitemos
   }

   Estos métodos "tuneados" van dentro de la clase que implementa Serializable,  y una vez que los llamemos desde el método main() completarán la serialización según nuestras necesidades. Aquí tenéis un ejemplo:


import java.io.*;
import java.util.*;
public class TestSer{
   public static void main(String[] args) throws Exception{
      FileOutputStream fos = new FileOutputStream("test.ser");
      ObjectOutputStream os = new ObjectOutputStream(fos);
      Prueba p = new Prueba(358);
      os.writeObject(p);
      os.close(); 

      FileInputStream fis = new FileInputStream("test.ser");
      ObjectInputStream is = new ObjectInputStream(fis);
      p = (Prueba) is.readObject();

      System.out.println("Datos: " + p.getDatos());
      System.out.println("Hora actual: " + p.getHoraActual());
      is.close();
   }
}

class Prueba implements Serializable{
   private static final long seriaVersionUID = 3L; //luego explico esto
   private transient int datos; //son transient pero necesitamos serializarlos
   private transient Date horaActual;//lo queremos sólo en el servidor receptor

   public Prueba(int datos){
      this.datos = datos;
      this.horaActual = new Date();
   }

   private void writeObject(ObjectOutputStream os){
      try{
         os.defaultWriteObject(); //el método por defecto
         /*ahora escribimos lo que queremos que haga*/
         os.writeInt(datos); 
         System.out.println("Serialización");
      }catch(IOException e){
         e.printStackTrace();
      }
   }

   private void readObject(ObjectInputStream is){
      try{
         is.defaultReadObject();
         datos = is.readInt();
         horaActual = new Date();
         System.out.println("Recuperación");
      }catch(IOException e){
         e.printStackTrace();
      }catch(ClassNotFoundException e){
         e.printStackTrace();
      }
   }

   public int getDatos(){
      return datos;
   }

   public Date getHoraActual(){
      return horaActual;
   }
}

   Voy a explicaros ahora qué es eso de serialVerionUID. Ocurre que normalmente la serialización sucede en una JVM, y la reconstrucción en otra. Pero la JVM le asigna un número de versión, el serialVersionUID, a la clase serializable. Ahora bien, si más adelante se añaden o modifican campos en un objeto ya serializado, ese objeto no se podrá reconstruir porque el número de versión de la clase emisora no coincidirá con el de la clase receptora. Por eso se recomienda escribir el propio número de versión declarando un campo llamado serialVersionUID, que debe ser static, final y de tipo long, preferentemente private.

   private static final long serialVersionUID = 17L;

   Veamos por último cómo afecta la herencia a la serialización. Ya hemos visto que si una superclase implementa Serializable, entonces todas sus subclases la implementarán también automáticamente. Pero, ¿qué ocurre si una subclase es serializable pero su superclase no lo es?.

   En la entrada anterior veíamos que las variables serializadas mantenían el valor que tuvieran antes de la serialización al ser recuperadas. Por el contrario, las marcadas como transient reciben el valor por defecto tras la serialización, es decir, null en el caso de referencias a objetos y 0 en el caso de tipos primitivos. Sin embargo, todas las variables heredadas de la superclase serán reconstruidas con los valores que tenían inicialmente asignados, y no los que tenían en el momento de la serialización. Esto ocurre porque el constructor de la superclase, al contrario de lo que pasa con el constructor de la clase serializable, se ejecutará. Veamos un ejemplo:



import java.io.*;
public class SuperClase{
   public static void main(String... args){
      Empleado emp = new Empleado(1000, "Reponedor");
      System.out.println("Antes de la serialización. "+ emp.categoría + ": " + emp.sueldo +"€");
      try{
         FileOutputStream fos = new FileOutputStream("archivo.ser");
         ObjectOutputStream os = new ObjectOutputStream(fos);
         os.writeObject(emp);
         os.close();
      }catch(Exception e){  //simplificamos para no alargar el ejemplo
         e.printStackTrace();
      }
      try{
         FileInputStream fis = new FileInputStream("archivo.ser");
         ObjectInputStream ois = new ObjectInputStream(fis);
         emp = (Empleado) ois.readObject();
         ois.close();
      }catch(Exception e){
         e.printStackTrace();
      }
      System.out.println("Después de la serialización. " + emp.categoría + ": " + emp.sueldo +"€");
   }
}

class Empleado extends Empresa implements Serializable{
   String categoría;
   Empleado(int s, String c){
      sueldo = s; //esto se hereda de la superclase
      categoría = c; 
   }
}

class Empresa{ //esta clase no es serializable
   int sueldo = 800;
}

Este programa produce la salida:
Antes de la serialización. Reponedor: 1000€
Después de la serialización. Repondedor: 800€


Como veis, hay que tener cuidado con esto, porque aunque los dos sean sueldos de miseria, no es lo mismo cobrar 1000€ que 800€. : )

9 comentarios:

  1. Muchas gracias, me sirve mucho tus artículos.

    ResponderEliminar
  2. Respuestas
    1. tengo unas dudas necesitaria una minima esesoria

      Eliminar
    2. Hola, Javier 1101, siento no haber podido responderte antes. ¿Podrías por favor ser más específico en tus necesidades?
      Un saludo.

      Eliminar
  3. Precisamente estoy en mi curso de certificación para Java Programmer SE 6, y tu artículo me ha ayudado mucho para comprender el tema de Serialización. Muchas gracias!!

    ResponderEliminar
    Respuestas
    1. Me alegro mucho, Víctor. ¡Espero que el examen te salga genial!

      Eliminar