CodeGym /Cursos /JAVA 25 SELF /Serialización de objetos anidados: particularidades

Serialización de objetos anidados: particularidades

JAVA 25 SELF
Nivel 44 , Lección 0
Disponible

1. Introducción: serialización de objetos con campos de referencia

En las aplicaciones reales rara vez se encuentran clases completamente «planas». Normalmente un objeto contiene otros objetos y estos, a su vez, pueden contener algo más. A esto se le llama composición (o anidamiento) de objetos. Por ejemplo:

public class Address {
    String city;
    String street;
}

public class Person {
    String name;
    int age;
    Address address; // ¡Objeto anidado!
}

Cuando serializamos un objeto así, surge la pregunta: ¿qué hacer con el campo address? ¿Debe Java serializarlo junto con Person? ¿Y si dentro de Address hay otro objeto más? Por suerte (o por desgracia — depende de la situación), Java por defecto serializa recursivamente todos los objetos anidados si ellos también implementan la interfaz Serializable.

La serialización en Java es siempre serialización profunda (deep serialization). Esto significa que se serializa no solo el propio objeto, sino también todos los objetos a los que hace referencia mediante sus campos (no transient), y así sucesivamente — hasta el «fondo».

Visualización del proceso

graph TD
    A[Person] --> B[Address]
    B --> C[CityInfo]
    A --> D[Pet]

En definitiva, si decides serializar Person, Java serializará también Address y todo lo que haya dentro de Address, y así sucesivamente.

2. Requisitos para los objetos anidados: ¡Serializable es obligatorio!

Seguramente ya te habrás fijado en un matiz importante: todos los objetos anidados que se serializan también deben implementar la interfaz Serializable.

Si al menos un campo de referencia apunta a un objeto que no implementa Serializable, el intento de serialización terminará con la excepción java.io.NotSerializableException.

Veamos algunos ejemplos de serialización profunda.

Ejemplo: todo correcto

import java.io.Serializable;

public class Address implements Serializable {
    String city;
    String street;
}

public class Person implements Serializable {
    String name;
    int age;
    Address address;
}

Ambas clases implementan Serializable. Todo funciona, la serialización se realiza con éxito.

Ejemplo: ¡error!

public class Address { // NO implementa Serializable!
    String city;
    String street;
}

public class Person implements Serializable {
    String name;
    int age;
    Address address;
}

Como vemos, aquí Address no implementa la interfaz Serializable. Por lo tanto, al serializar Person obtendremos la excepción NotSerializableException.

3. Ejemplo: serialización y deserialización con un objeto anidado

Veamos cómo se ve en la práctica. En el nivel anterior trabajamos con la aplicación «Gestor de contactos». Ahora añadamos la dirección de cada usuario.

import java.io.*;

class Address implements Serializable {
    String city;
    String street;

    Address(String city, String street) {
        this.city = city;
        this.street = street;
    }
}

class Person implements Serializable {
    String name;
    int age;
    Address address;

    Person(String name, int age, Address address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }
}

public class SerializationDemo {
    public static void main(String[] args) throws Exception {
        Person p = new Person("Iván", 30, new Address("Praga", "Slavinska, 1"));
        // Serialización
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("person.ser"));
        out.writeObject(p);
        out.close();

        // Deserialización
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("person.ser"));
        Person restored = (Person) in.readObject();
        in.close();

        System.out.println(restored.name + ", " + restored.age + ", " +
                           restored.address.city + ", " + restored.address.street);
    }
}

Resultado:

Iván, 30, Praga, Slavinska, 1

Todo funciona: el objeto anidado Address se ha serializado y restaurado junto con Person.

4. ¿Qué pasa si el objeto anidado no es serializable?

Si intentas serializar un objeto cuyo al menos un campo de referencia apunta a un objeto que no implementa Serializable, Java lanzará una excepción ya en el intento de serialización.

Demostración del error

class Address { // no es Serializable!
    String city;
    String street;
}

class Person implements Serializable {
    String name;
    Address address;
}

public class Test {
    public static void main(String[] args) throws Exception {
        Person p = new Person();
        p.name = "Peter";
        p.address = new Address();
        p.address.city = "Derry";
        p.address.street = "Elm";
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("person.ser"));
        out.writeObject(p); // <-- ¡HABRÁ UNA EXCEPCIÓN!
        out.close();
    }
}

Error:

java.io.NotSerializableException: Address

5. Transient para objetos anidados

¿Qué hacer si tienes un campo de referencia a un objeto que no debe serializarse (por ejemplo, una caché, una conexión a la base de datos, un objeto temporal)? En tal caso, declara el campo como transient. Entonces Java simplemente omitirá este campo durante la serialización.

Ejemplo con transient

class Address { // no es Serializable
    String city;
    String street;
}

class Person implements Serializable {
    String name;
    transient Address address; // transient!

    Person(String name, Address address) {
        this.name = name;
        this.address = address;
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
        Address addr = new Address();
        addr.city = "Los Santos";
        addr.street = "Mulholland Drive";
        Person p = new Person("Alexander", addr);

        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("person.ser"));
        out.writeObject(p);
        out.close();

        // Deserialización
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("person.ser"));
        Person restored = (Person) in.readObject();
        in.close();

        System.out.println(restored.name); // "Alexander"
        System.out.println(restored.address); // null!
    }
}

Resultado:
El campo address después de la deserialización es null, porque es transient.

6. Anidamiento profundo y recursión

La serialización funciona de forma recursiva: si Person tiene un campo Address, y Address tiene un campo CityInfo, y así sucesivamente, el serializador irá «profundizando» hasta que encuentre algo no serializable o hasta que se acabe la memoria (broma, pero solo a medias).

Importante: referencias cíclicas

El serializador de Java sabe trabajar con referencias cíclicas. Si, por ejemplo, un objeto tiene una referencia a otro objeto que a su vez referencia de vuelta, el serializador no entrará en un bucle, sino que guardará la estructura cuidadosamente.

class A implements Serializable {
    B b;
}
class B implements Serializable {
    A a;
}

Si creas objetos A y B que se referencian mutuamente, la serialización no provocará StackOverflowError — Java recuerda los objetos ya serializados.

7. Ejemplo: serialización de un objeto con una lista anidada

A menudo dentro de un objeto hay colecciones de otros objetos. Por ejemplo, un usuario puede tener una lista de amigos:

import java.io.*;
import java.util.*;

class Person implements Serializable {
    String name;
    List<Person> friends;

    Person(String name) {
        this.name = name;
        this.friends = new ArrayList<>();
    }
}

public class FriendsSerialization {
    public static void main(String[] args) throws Exception {
        Person alice = new Person("Alice");
        Person bob = new Person("Bob");
        alice.friends.add(bob);

        // Serialización
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("friends.ser"));
        out.writeObject(alice);
        out.close();

        // Deserialización
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("friends.ser"));
        Person restored = (Person) in.readObject();
        in.close();

        System.out.println(restored.name); // Alice
        System.out.println(restored.friends.get(0).name); // Bob
    }
}

Importante: Las colecciones de la biblioteca estándar de Java (ArrayList, HashMap, etc.) implementan Serializable, así que todo funciona «de serie».

8. Errores típicos al serializar objetos anidados

Error n.º 1: uno de los objetos anidados no implementa Serializable. Olvidaste añadir implements Serializable en una de las clases anidadas. Resultado: NotSerializableException en el primer intento de serialización. Comprueba que todas las clases en tu cadena de serialización soporten esta interfaz.

Error n.º 2: un campo no serializable no está declarado como transient. Si tienes un campo que no debe serializarse (por ejemplo, un flujo, una conexión a la base de datos, algo temporal), pero no lo declaraste como transient, la serialización fallará con un error. ¡No olvides transient!

Error n.º 3: incompatibilidad de serialVersionUID en las clases anidadas. Si declaras explícitamente serialVersionUID en las clases anidadas y cambias su estructura, no olvides actualizar este identificador — de lo contrario pueden producirse errores durante la deserialización.

Error n.º 4: objetos anidados mutables (modificables). Si serializas una colección o un objeto que luego cambia (por ejemplo, una lista de amigos), después de la deserialización será una «instantánea» del momento de la serialización. Los cambios posteriores en el original no se reflejarán en el objeto deserializado.

Error n.º 5: serialización de grafos de objetos enormes. Si tienes una estructura muy compleja con multitud de objetos anidados, la serialización puede consumir mucho tiempo y memoria. A veces es mejor serializar solo los datos clave y no toda la estructura completa.

1
Tarea
JAVA 25 SELF, nivel 44, lección 0
Bloqueada
Registro digital de datos personales 🏢
Registro digital de datos personales 🏢
1
Tarea
JAVA 25 SELF, nivel 44, lección 0
Bloqueada
Gestión de la logística de entrega compleja: datos profundamente anidados 🌍
Gestión de la logística de entrega compleja: datos profundamente anidados 🌍
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION