1. Interfaz Serializable
¿Recuerdas los ejemplos de la lección anterior? Las clases de Java que queremos serializar deben implementar la interfaz especial — java.io.Serializable. Es el llamado interfaz marcador: no contiene ningún método y simplemente «marca» la clase como apta para la serialización. Si la clase implementa esta interfaz, la JVM permite serializar sus objetos con los medios estándar.
No es recomendable serializar todo sin más, porque no todos los objetos se pueden o se deben guardar en bytes. Algunos objetos dependen del estado del sistema operativo, de archivos abiertos o de conexiones de red. Por eso Java exige marcar explícitamente la clase como serializable.
Un interfaz marcador es como una pegatina de «apto para embalaje» en una caja. Si no hay tal pegatina, el embalador (la JVM) se niega a trabajar.
Ejemplo: declaración de una clase serializable
import java.io.Serializable;
public class User implements Serializable {
private String name;
private int age;
// Constructor, getters y setters
public User(String name, int age) {
this.name = name;
this.age = age;
}
// Por estética: método toString()
@Override
public String toString() {
return "User{name='" + name + "', age=" + age + "}";
}
}
Ten en cuenta:
- Simplemente añadimos implements Serializable a la declaración de la clase.
- No es necesario implementar ningún método (la interfaz está vacía).
- Todas las clases estándar de Java que se pueden serializar (por ejemplo, ArrayList, HashMap, String) ya implementan Serializable.
2. Cómo hacer que tu clase sea serializable
Regla n.º 1: simplemente añade implements Serializable
Eso es todo lo que se requiere para la clase. ¡Pero hay matices!
Importante: todos los objetos anidados también deben ser serializables.
Si tu clase tiene campos que son referencias a otros objetos, estos también deben ser serializables. Por ejemplo:
public class Profile implements Serializable {
private User user; // ¡User debe ser serializable!
private int level;
}
Si al menos uno de los campos no es serializable, al intentar serializar se producirá una excepción.
3. Ejemplo de serialización y deserialización
Veamos cómo serializar y deserializar un objeto a un archivo. Para ello se usan las clases ObjectOutputStream y ObjectInputStream.
Ejemplo: serializar un objeto User a un archivo
import java.io.*;
public class SerializeDemo {
public static void main(String[] args) {
User user = new User("Alice", 30);
// Guardamos el objeto en un archivo
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.ser"))) {
oos.writeObject(user);
System.out.println("El objeto se ha serializado correctamente en el archivo user.ser");
} catch (IOException e) {
System.out.println("Error de serialización: " + e.getMessage());
}
}
}
¿Qué ocurre aquí?
- Creamos un objeto User.
- Abrimos un flujo ObjectOutputStream, que escribe en el archivo "user.ser".
- Llamamos a writeObject(user). En ese momento la JVM convierte el objeto en un flujo de bytes y lo guarda en el archivo.
Ejemplo: deserializar un objeto desde un archivo
import java.io.*;
public class DeserializeDemo {
public static void main(String[] args) {
// Leemos el objeto del archivo
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.ser"))) {
User user = (User) ois.readObject();
System.out.println("El objeto se ha restaurado correctamente: " + user);
} catch (IOException | ClassNotFoundException e) {
System.out.println("Error de deserialización: " + e.getMessage());
}
}
}
¿Qué ocurre aquí?
- Abrimos un flujo ObjectInputStream, que lee del archivo "user.ser".
- Llamamos a readObject(). La JVM restaura el objeto a partir de los bytes.
- No olvides convertir el resultado al tipo necesario (User), ya que readObject() devuelve Object.
- Puede producirse ClassNotFoundException si la clase User no se encuentra durante la deserialización.
Todo junto: serialización y deserialización
import java.io.*;
public class SerializationExample {
public static void main(String[] args) {
User user = new User("Bob", 22);
// Serialización
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.ser"))) {
oos.writeObject(user);
System.out.println("¡Serialización completada!");
} catch (IOException e) {
System.out.println("Error de serialización: " + e.getMessage());
}
// Deserialización
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.ser"))) {
User loaded = (User) ois.readObject();
System.out.println("¡Deserialización completada! " + loaded);
} catch (IOException | ClassNotFoundException e) {
System.out.println("Error de deserialización: " + e.getMessage());
}
}
}
Resultado:
¡Serialización completada!
¡Deserialización completada! User{name='Bob', age=22}
4. Qué ocurre «bajo el capó» durante la serialización
Cuando llamas a writeObject, la JVM primero verifica si la clase implementa la interfaz Serializable. Si la clase no está marcada como serializable, se lanza una excepción. Luego la JVM recorre todos los campos normales del objeto (es decir, aquellos que no son static ni transient) y escribe sus valores en el flujo de bytes. Si entre esos campos hay otros objetos, la serialización se aplica a ellos recursivamente, pero solo si también implementan Serializable.
En la deserialización, el objeto se crea sin invocar el constructor habitual y sus campos se rellenan con los valores guardados — como si un «constructor sin constructor» devolviera el objeto a la vida a partir del flujo de bytes.
Algunos campos no se serializan. Los campos estáticos (static) pertenecen a la clase en sí, no a un objeto concreto, por lo que sus valores no se guardan. Los campos marcados como transient también se omiten — esto es útil para datos temporales, caché o información confidencial como contraseñas.
Esquema del proceso de serialización
flowchart TB
A[Objeto User en memoria] -- writeObject --> B[ObjectOutputStream]
B -- guarda bytes --> C[Archivo user.ser]
C -- readObject --> D[ObjectInputStream]
D -- restaura --> E[Objeto User en memoria]
5. Errores típicos al usar Serializable
Error n.º 1: campo-referencia a un objeto no serializable. Si en la clase User aparece un campo de tipo, por ejemplo, Thread o Socket, la serialización no funcionará. No todos los objetos se pueden serializar — ¡tenlo presente!
Error n.º 2: clases internas no serializables. Si la clase User contiene una clase interna que no es static, la serialización puede no funcionar. Es mejor usar clases internas static o clases en archivos separados.
Error n.º 3: intentar serializar un campo static. Los campos estáticos no se serializan — pertenecen a la clase, no al objeto. Tras la deserialización, el campo static tendrá el valor definido en la clase, no en el objeto serializado.
Error n.º 4: desajuste de versiones de la clase. Si cambias la estructura de la clase después de la serialización (por ejemplo, añades o eliminas un campo) y luego intentas deserializar un objeto antiguo, puede producirse el error InvalidClassException. Para el control de versiones se usa el campo especial serialVersionUID — hablaremos de él con más detalle en la próxima lección.
GO TO FULL VERSION