CodeGym /Cursos /JAVA 25 SELF /ObjectOutputStream, ObjectInputStream: trabajo con flujos...

ObjectOutputStream, ObjectInputStream: trabajo con flujos

JAVA 25 SELF
Nivel 42 , Lección 3
Disponible

1. Introducción

En Java, la serialización solo funciona con aquellos objetos que lo permiten explícitamente. Para ello, la clase debe implementar una interfaz especial — java.io.Serializable.

import java.io.Serializable;

public class Person implements Serializable {
    // Campos, constructores, métodos
}

Serializable — es una interfaz marcadora: no tiene métodos; simplemente le dice a la JVM: «esta clase se puede serializar, ¡no hay problema!». Si intentas serializar un objeto de una clase que no implementa Serializable, obtendrás la excepción NotSerializableException. Incluso si al menos un campo (o un objeto anidado) no es serializable — la serialización no funcionará.

ObjectOutputStream y ObjectInputStream

  • ObjectOutputStream — clase que escribe objetos en un flujo (por ejemplo, en un archivo o a través de la red).
  • ObjectInputStream — clase que lee objetos de un flujo.

Funcionan en pareja: uno serializa el objeto y el otro — lo deserializa.

Métodos principales

  • writeObject(Object obj) — serializa un objeto y lo escribe en el flujo.
  • readObject() — lee un objeto del flujo, lo deserializa y lo devuelve.

Importante: ambas clases trabajan sobre los flujos de E/S normales (OutputStream y InputStream). Lo más habitual es usarlas junto con flujos de archivo — FileOutputStream y FileInputStream —, pero también pueden emplearse con flujos de red.

2. Ejemplo de serialización

Escribamos un ejemplo sencillo: serializaremos y deserializaremos un objeto de la clase Person.

Paso 1. Definimos la clase

import java.io.Serializable;

public class Person implements Serializable {
    private String name;
    private int age;
    // el campo transient no se serializará
    private transient String secret;

    public Person(String name, int age, String secret) {
        this.name = name;
        this.age = age;
        this.secret = secret;
    }

    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + ", secret='" + secret + "'}";
    }
}

Paso 2. Serialización del objeto a un archivo

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;

public class SerializeDemo {
    public static void main(String[] args) throws Exception {
        Person person = new Person("Alice", 30, "likes pizza");

        // Creamos un flujo para escribir en un archivo
        FileOutputStream fileOut = new FileOutputStream("person.bin");
        ObjectOutputStream out = new ObjectOutputStream(fileOut);

        // Guardamos el objeto
        out.writeObject(person);

        // Cerramos los flujos
        out.close();
        fileOut.close();

        System.out.println("Objeto serializado en el archivo person.bin");
    }
}

Paso 3. Deserialización del objeto desde el archivo

import java.io.FileInputStream;
import java.io.ObjectInputStream;

public class DeserializeDemo {
    public static void main(String[] args) throws Exception {
        // Abrimos el flujo para leer del archivo
        FileInputStream fileIn = new FileInputStream("person.bin");
        ObjectInputStream in = new ObjectInputStream(fileIn);

        // Restauramos el objeto
        Person person = (Person) in.readObject();

        in.close();
        fileIn.close();

        System.out.println("Objeto deserializado: " + person);
    }
}

Salida esperada

Objeto serializado en el archivo person.bin
Objeto deserializado: Person{name='Alice', age=30, secret='null'}

¡Atención! El campo transient no se serializa. Tras la deserialización, será igual a null. Esto es importante para datos temporales o sensibles.

3. Limitaciones y características

Todos los campos deben ser serializables

Si una clase tiene campos que no implementan Serializable (o contienen tales objetos), la serialización terminará con error. Por ejemplo, los campos de tipo Thread o Socket no se pueden hacer serializables «sin más».

Campos static y transient

  • Campos estáticos (static) no se serializan: pertenecen a la clase y no a un objeto concreto.
  • Campos transient — los marcados como transient se excluyen explícitamente de la serialización y, tras la restauración, reciben los valores por defecto (null, 0, etc.).

Excepciones

  • Intentar serializar un objeto que no implementa Serializable provoca NotSerializableException.
  • Durante la deserialización pueden producirse errores: archivo no encontrado, discrepancia de clases, datos corruptos, etc.

Versiones de las clases

Si cambias la estructura de la clase después de la serialización (por ejemplo, añades/eliminás campos), durante la deserialización puede producirse InvalidClassException. Para el control de versiones se utiliza el campo especial serialVersionUID (más detalles en una de las próximas lecciones).

4. Práctica: serialización y deserialización de un objeto a un archivo

Supongamos que tenemos la clase Person y queremos guardar una lista de personas en un archivo y leerla de vuelta.

Clase Person (serializable)

import java.io.Serializable;

public class Person implements Serializable {
    private String name;
    private int age;
    private transient String secret; // no se serializará

    public Person(String name, int age, String secret) {
        this.name = name;
        this.age = age;
        this.secret = secret;
    }

    @Override
    public String toString() {
        return name + " (" + age + "), secret: " + secret;
    }
}

Serialización de la lista de personas

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;

public class SerializeListDemo {
    public static void main(String[] args) throws Exception {
        List<Person> people = new ArrayList<>();
        people.add(new Person("Alice", 30, "likes pizza"));
        people.add(new Person("Bob", 25, "hates broccoli"));

        FileOutputStream fileOut = new FileOutputStream("people.bin");
        ObjectOutputStream out = new ObjectOutputStream(fileOut);

        // Guardamos la lista
        out.writeObject(people);

        out.close();
        fileOut.close();

        System.out.println("Se ha serializado la lista de personas.");
    }
}

Deserialización de la lista de personas

import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.util.List;

public class DeserializeListDemo {
    public static void main(String[] args) throws Exception {
        FileInputStream fileIn = new FileInputStream("people.bin");
        ObjectInputStream in = new ObjectInputStream(fileIn);

        // Restauramos la lista
        List<Person> people = (List<Person>) in.readObject();

        in.close();
        fileIn.close();

        for (Person p : people) {
            System.out.println(p);
        }
    }
}

Resultado:

Alice (30), secret: null
Bob (25), secret: null

5. Errores típicos

Error n.º 1: la clase no implementa Serializable. Si olvida añadir implements Serializable, al intentar serializar se producirá NotSerializableException. Es el error más frecuente y sencillo.

Error n.º 2: campo no serializable. Si el objeto contiene un campo que no es serializable (por ejemplo, Thread, Socket o cualquier otro tipo sin Serializable), la serialización fallará. Estos campos deben marcarse como transient o hacerse serializables.

Error n.º 3: cambio de la estructura de la clase. Si el objeto se serializó y después la clase cambió (se añadieron/eliminaron campos), al leer puede aparecer InvalidClassException. Especifique serialVersionUID para un control de versión estable.

Error n.º 4: intento de serializar campos estáticos. Los campos con el modificador static no se serializan. Tras la deserialización, sus valores serán los que tenga la clase por defecto, y no los que había al serializar.

Error n.º 5: flujos sin cerrar. Si no se cierran los flujos después de trabajar con ellos, se puede obtener un archivo corrupto o una fuga de recursos. Utilice try-with-resources o cierre los flujos explícitamente.

Error n.º 6: discrepancia de clases. Si la clase se ha renombrado o movido a otro paquete, la deserialización no funcionará — se requiere una coincidencia exacta del nombre y del paquete de la clase guardada en el flujo.

Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION