Introducción
La serialización es una técnica en Java que permite convertir un objeto en una secuencia de bytes para su almacenamiento o transmisión. Esto es útil para guardar el estado de un objeto en un archivo o para enviarlo a través de una red. Posteriormente, mediante la deserialización, se puede reconstruir el objeto a partir de estos bytes. En este artículo, explicaremos en detalle cómo funciona la serialización de objetos en Java y cómo utilizarla en la práctica con ejemplos.
Conceptos clave de la serialización
- Serialización: El proceso de convertir un objeto en una secuencia de bytes para que pueda ser almacenado o transmitido.
- Deserialización: El proceso inverso, donde los bytes almacenados o transmitidos se convierten de nuevo en un objeto.
- Interfaz
Serializable
: Un objeto debe implementar esta interfaz para ser serializable. No requiere la implementación de métodos, solo sirve para marcar que un objeto es apto para la serialización.
Ejemplo básico de serialización
Imaginemos que tenemos una clase Persona
que queremos serializar. Para ello, esta clase debe implementar la interfaz Serializable
.
import java.io.Serializable;
public class Persona implements Serializable {
private String nombre;
private String telefono;
private String email;
private String nacimiento;
public Persona(String nombre, String telefono, String email, String nacimiento) {
this.nombre = nombre;
this.telefono = telefono;
this.email = email;
this.nacimiento = nacimiento;
}
@Override
public String toString() {
return "Nombre: " + nombre + ", Teléfono: " + telefono + ", Email: " + email + ", Nacimiento: " + nacimiento;
}
}
Serialización de un objeto
Para serializar un objeto, utilizamos la clase ObjectOutputStream
, que nos permite escribir objetos en un archivo de manera binaria.
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class SerializarObjeto {
public static void main(String[] args) {
Persona persona = new Persona("Juan", "123456789", "juan@gmail.com", "01/01/1990");
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("persona.dat"))) {
oos.writeObject(persona);
System.out.println("Objeto serializado con éxito.");
} catch (IOException e) {
e.printStackTrace();
}
}
}
Explicación del código:
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("persona.dat"));
: Crea un flujo para escribir el objetopersona
en un archivo llamado «persona.dat».oos.writeObject(persona);
: Serializa el objetopersona
y lo guarda en el archivo.- El flujo se cierra automáticamente al final gracias al bloque
try-with-resources
.
Deserialización de un objeto
Para leer un objeto serializado de un archivo, utilizamos la clase ObjectInputStream
, que nos permite reconstruir el objeto desde el archivo binario.
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class DeserializarObjeto {
public static void main(String[] args) {
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("persona.dat"))) {
Persona persona = (Persona) ois.readObject();
System.out.println("Objeto deserializado con éxito: " + persona);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
Explicación del código:
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("persona.dat"));
: Abre el archivo «persona.dat» para leer el objeto almacenado.Persona persona = (Persona) ois.readObject();
: Deserializa el objeto del archivo y lo convierte de nuevo a un objeto de tipoPersona
.- Se maneja la excepción
ClassNotFoundException
porquereadObject()
requiere que la clase del objeto deserializado esté disponible en el programa.
Problema común: Múltiples cabeceras en el archivo
Cuando escribimos varios objetos en un archivo usando ObjectOutputStream
, se añade una cabecera de metadatos para cada objeto. Si se vuelve a abrir el archivo para añadir más objetos, esta cabecera adicional puede generar una excepción StreamCorruptedException
cuando intentamos leer los objetos.
Solución: Redefinir ObjectOutputStream
Una solución para evitar el problema de las cabeceras adicionales es redefinir la clase ObjectOutputStream
y sobreescribir el método writeStreamHeader()
para que no se escriba una nueva cabecera.
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
class MiObjectOutputStream extends ObjectOutputStream {
public MiObjectOutputStream(FileOutputStream fos) throws IOException {
super(fos);
}
@Override
protected void writeStreamHeader() throws IOException {
// No hacer nada para evitar escribir una nueva cabecera
}
}
Uso de la clase redefinida:
import java.io.FileOutputStream;
import java.io.IOException;
public class SerializarMultiplesObjetos {
public static void main(String[] args) {
Persona persona1 = new Persona("Ana", "987654321", "ana@gmail.com", "02/02/1992");
Persona persona2 = new Persona("Luis", "112233445", "luis@gmail.com", "03/03/1993");
try (FileOutputStream fos = new FileOutputStream("personas.dat");
ObjectOutputStream oos = new ObjectOutputStream(fos)) {
oos.writeObject(persona1);
} catch (IOException e) {
e.printStackTrace();
}
try (FileOutputStream fos = new FileOutputStream("personas.dat", true);
MiObjectOutputStream oos = new MiObjectOutputStream(fos)) {
oos.writeObject(persona2);
} catch (IOException e) {
e.printStackTrace();
}
}
}
Explicación del código:
- En la primera escritura, usamos el
ObjectOutputStream
normal para añadir la cabecera. - En las escrituras posteriores, usamos nuestra clase redefinida
MiObjectOutputStream
para evitar que se añadan cabeceras adicionales.
Conclusión
La serialización de objetos en Java es una herramienta poderosa para almacenar y transmitir objetos. Nos permite convertir un objeto en una secuencia de bytes, almacenarlo en un archivo y reconstruirlo más tarde mediante la deserialización. Es fundamental tener en cuenta los posibles problemas con las cabeceras cuando se manejan múltiples objetos en un archivo, y la solución presentada ofrece una forma efectiva de gestionar este escenario.