ObjectOutputStream y OutputInputStream

ObjectOuptutStream y ObjectInputStream son clases envolventes, es decir, clases que aceptan otro stream por debajo y que reenvían las llamadas a read o write que reciban pero alteradas. En este caso, para poder leer o escribir otros tipos de datos que no sean bytes puros, ocupándose de la codificación y decodificación.

Cuando trabajamos con flujos de datos en Java, una herramienta muy útil que podemos utilizar es el ObjectOutputStream. Este tipo de OutputStream es envolvente, lo que significa que se construye a partir de otro OutputStream que le pasamos como parámetro. La magia ocurre porque cuando llamamos a los métodos de escritura del ObjectOutputStream, internamente se transforman esas llamadas y se delegan al OutputStream subyacente, pero con los datos convertidos de una forma especial.

Esta transformación nos permite guardar datos de distintos tipos primitivos, como enteros, doubles o booleanos, sin tener que preocuparnos por convertirlos manualmente a bytes. Por ejemplo, si tenemos un valor double como 3.5, con un FileOutputStream normal no podríamos escribirlo directamente, ya que solo acepta bytes. Sin embargo, con un ObjectOutputStream podemos llamar a writeDouble(3.5) y el flujo se encargará de convertir ese número en su representación binaria adecuada y almacenarlo en el archivo.

Para usarlo, primero creamos un FileOutputStream apuntando al archivo donde queremos guardar los datos, y luego envolvemos ese flujo con un ObjectOutputStream. Así podemos acceder a métodos específicos como writeByte, writeChar, writeDouble, writeFloat y otros, que no están disponibles en la clase base OutputStream. Esto nos facilita mucho la tarea de escribir datos complejos en archivos.

Por ejemplo, podemos guardar en un archivo llamado Objetos.txt valores como 3.5 (double), true (boolean) y una secuencia de números largos. Al abrir este archivo con un editor de texto, no veremos los valores en forma legible, sino que aparecerán como caracteres extraños, porque en realidad están almacenados en formato binario. Esto es normal, ya que el archivo está pensado para ser leído con un ObjectInputStream, no con un editor de texto.

Para leer estos datos, utilizamos el ObjectInputStream, que funciona de manera inversa al ObjectOutputStream. Primero creamos un FileInputStream apuntando al archivo que contiene los datos binarios, y luego lo envolvemos con un ObjectInputStream. Este flujo nos permite leer los datos en el mismo orden en que fueron escritos, utilizando métodos como readDouble, readBoolean, readLong o readUTF para cadenas.

Es importante respetar el orden de lectura, ya que si escribimos primero un double, luego un boolean y después varios long, debemos leerlos en ese mismo orden para que los datos se interpreten correctamente. Si intentamos leer el archivo con un flujo que solo maneje bytes, no podremos interpretar los datos correctamente, ya que están codificados en binario.

Para manejar estos flujos de forma segura y sencilla, podemos utilizar la estructura try-with-resources, que nos permite abrir varios flujos en una misma declaración y garantiza que se cierren automáticamente al finalizar la operación, evitando fugas de recursos.

Un ejemplo básico para escribir datos con ObjectOutputStream sería:

try (FileOutputStream fos = new FileOutputStream("Objetos.txt");
     ObjectOutputStream oos = new ObjectOutputStream(fos)) {
    oos.writeDouble(3.5);
    oos.writeBoolean(true);
    oos.writeLong(1L);
    oos.writeLong(2L);
    oos.writeLong(3L);
    oos.writeLong(4L);
    oos.writeLong(5L);
    oos.writeLong(6L);
    oos.writeLong(7L);
    oos.writeLong(8L);
    oos.writeLong(9L);
}

Y para leer esos datos en el mismo orden:

try (FileInputStream fis = new FileInputStream("Objetos.txt");
     ObjectInputStream ois = new ObjectInputStream(fis)) {
    double valorDouble = ois.readDouble();
    boolean valorBoolean = ois.readBoolean();
    long[] valoresLong = new long[9];
    for (int i = 0; i < valoresLong.length; i++) {
        valoresLong[i] = ois.readLong();
    }
    System.out.println("Double leído: " + valorDouble);
    System.out.println("Boolean leído: " + valorBoolean);
    System.out.print("Longs leídos: ");
    for (long val : valoresLong) {
        System.out.print(val + " ");
    }
}

Así, podemos almacenar y recuperar datos de distintos tipos de forma sencilla y segura, sin preocuparnos por la conversión manual a bytes. Esta técnica es especialmente útil cuando queremos guardar información compleja en archivos y luego recuperarla sin pérdida de precisión ni necesidad de parsear manualmente los datos.

Lista de reproducción
  1. 1
    Introducción al taller
    2 minutos
  2. 2
    Qué es la entrada y salida
    4 minutos
  3. 3
    Streams de entrada y salida en Java
    7 minutos
  4. 4
    FileOutputStream
    9 minutos
  5. 5
    FileInputStream
    13 minutos
  6. 6
    try-with-resources
    2 minutos
  7. 7
    ObjectOutputStream y OutputInputStream
    6 minutos
  8. 8
    Object streams con Strings y Objects
    6 minutos
  9. 9
    Serializando clases
    7 minutos
  10. 10
    Los peligros de Serializable
    4 minutos
  11. 11
    BufferedOutputStream
    6 minutos
  12. 12
    Envolviendo varios OutputStreams
    2 minutos
  13. 13
    BufferedInputStream y rebobinado de streams
    9 minutos
  14. 14
    Readers y Writers en Java
    3 minutos
  15. 15
    Writer y FileWriter
    4 minutos
  16. 16
    Reader, FileReader y BufferedReader
    4 minutos
  17. 17
    InputStreamReader y OutputStreamWriter
    6 minutos
  18. 18
    PrintStream y PrintWriter
    5 minutos