Serialización de objetos en java

De ChuWiki
Saltar a: navegación, buscar

Contenido

Serialización de un objeto: Implementar Serializable

Para que un programa java pueda convertir un objeto en un montón de bytes y pueda luego recuperarlo, el objeto necesita ser Serializable. Al poder convertir el objeto a bytes, ese objeto se puede enviar a través de red, guardarlo en un fichero, y después reconstruirlo al otra lado de la red, leerlo del fichero,....

Para que un objeto sea serializable basta con que implemente la interfaz Serializable. Como la interfaz Serializable no tiene métodos, es muy sencillo implementarla, basta con un implements Serializable y nada más. Por ejemplo, la clase Datos siguiente es Serializable y java sabe perfectamente enviarla o recibirla por red, a través de socket o de rmi. También java sabe escribirla en un fichero o reconstruirla a partir del fichero.

public class Datos implements Serializable
{
   public int a;
   public String b;
   public char c;
}

Si dentro de la clase hay atributos que son otras clases, éstos a su vez también deben ser Serializable. Con los tipos de java (String, Integer, etc.) no hay problema porque lo son. Si ponemos como atributos nuestras propias clases, éstas a su vez deben implementar Serializable. Por ejemplo

/* Esta clase es Serializable porque implementa Serializable y todos sus
 *  campos son Serializable, incluido "Datos f;"
 */
public class DatoGordo implements Serializable
{
   public int d;
   public Integer e;
   Datos f;
}

Serialización a medida

En realidad, se llama "serializar un objeto" al proceso de convertirlo a bytes, para poder enviarlo por una red, y reconstruirlo luego a partir de esos bytes.

A veces podemos necesitar que se haga algo especial en el momento de serializarlo, bien al construirlo a bytes, bien al recibirlo. Por ello java nos permite hacerlo. Basta con hacer que nuestro objeto defina uno o los dos métodos siguientes:

private void readObject(java.io.ObjectInputStream stream)
     throws IOException, ClassNotFoundException
{
   // Aqui debemos leer los bytes de stream y reconstruir el objeto
}

private void writeObject(java.io.ObjectOutputStream stream)
     throws IOException
{
   // Aquí escribimos en stream los bytes que queramos que se envien por red.
}

java llamará a estos métodos cuando necesite convertirlo a bytes para enviarlo por red o cuando necesite reconstruirlo a partir de los bytes en red.

Convertir un Serializable a byte[] y viceversa

Podemos convertir cualquier objeto Serializable a un array de byte y viceversa. Normalmente esto no es necesario que lo hagamos explícitamente en el código para enviar el objeto por un socket o escribirlo en un fichero puesto que contamos con las clases ObjectInputStream y ObjectOutputStream que se encargan de ello. Sin embargo, en ocasiones, por ejemplo, al intentar enviar un objeto por un socket udp, sí es necesario hacerlo manualmente.

Para hacer esta conversión, podemos usar este código

De objeto a byte[]

ByteArrayOutputStream bs= new ByteArrayOutputStream();
ObjectOutputStream os = new ObjectOutputStream (bs);
os.writeObject(unObjetoSerializable);  // this es de tipo DatoUdp
os.close();
byte[] bytes =  bs.toByteArray(); // devuelve byte[]

De byte[] a objeto

ByteArrayInputStream bs= new ByteArrayInputStream(bytes); // bytes es el byte[]
ObjectInputStream is = new ObjectInputStream(bs);
ClaseSerializable unObjetoSerializable = (ClaseSerializable)is.readObject();
is.close();

Una utilidad de estos dos códigos es el realizar copias "profundas" de un objeto, es decir, se obtiene copia del objeto, copias de los atributos y copias de los atributos de los atributos. Basta convertir el objeto a byte[] y luego reconstruirlo en otra variable.


Serial Version UID

Cuando pasamos objetos Serializable de un lado a otro tenemos un pequeño problema. Si la clase que queremos pasar es objeto_serializable, lo normal, salvo que usemos Carga dinámica de clases, es que en ambos lados (el que envía y el que recibe la clase), tengan su propia copia del fichero objeto_serializable.class. Es posible que en distintas versiones de nuestro programa la clase objeto_serializable cambie, de forma que es posible que un lado tenga una versión más antigua que en el otro lado. Si sucede esto, la reconstrucción de la clase en el lado que recibe es imposible.

Para evitar este problema, se aconseja que la clase objeto_serializable tenga un atributo privado de esta forma

 private static final long serialVersionUID = 8799656478674716638L;

de forma que el numerito que ponemos al final debe ser distinto para cada versión de compilado que tengamos.

De esta forma, java es capaz de detectar rápidamente que las versiones de objeto_serializable.class en ambos lados son distintas.

Algunos entornos de desarrollo, como eclipse, dan un warning si una clase que implementa Serializable (o hereda de una clase que a su vez implementa Serializable) no tiene definido este campo. Es más, puede generarlo automáticamente, número incluido, si se lo pedimos. En eclipse basta con hacer click con el ratón sobre el símbolo de warning para que nos de las posibles soluciones al warning. Una de ellas genera el número automáticamente.

Por donde continuar

La serialización sirve básicamente, para dos cosas:

Serialización a XML o a JSON