🇺🇦 Слава Україні! Consulta cómo puedes ayudar a Ucrania desde España u otros países en supportukrainenow.org.

Java IO

ObjectOutputStream y OutputInputStream

• Duración: 6:15

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.

Otro tipo de OutputStream va a ser el ObjectOutputStream, que es un OutputStream envolvente. La idea de este OutputStream va a consistir en proporcionarle como parámetro otro OutputStream, y lo que va a ocurrir es que cuando llamemos al de fuera, se van a transformar las llamadas que hagamos a write(), y se va a llamar al write() de dentro, pero con opciones transformadas. Es una forma de decorarlo, de tal forma que lo que se guarde en el OutputStream de salida sea ligeramente diferente a lo que le hemos dicho al de fuera.

En este caso, yo le tengo que proporcionar uno, así que voy a copiar el que he estado usando antes de tipo FileOutputStream, aunque lo voy a guardar como objetos.txt para que sea distinto. Ahora, lo que voy a hacer es pedirle a ObjectOutputStream que guarde en éste. Tiene más sentido usar una variable de tipo ObjectOutputStream, porque así podemos tener acceso a los métodos extra de ObjectOutputStream que podemos usar a nuestra disposición como writeByte(), writeChar(), writeDouble()... y similares. Estos no forman parte de OutputStream, sólo forman parte de ObjectOutputStream. Es precisamente la razón por la que querremos instanciar un ObjectOutputStream en primer lugar.

El funcionamiento de esta clase va a consistir en que nosotros, en vez de escribir bytes, vamos a tener la opción de pasarle valores más coherentes como writeInt(), writeDouble(), writeChar()... así, podemos pasarle valores de distintos tipos. Por ejemplo, si tuviese un valor de tipo Float o Double, que fuese 3.5f o 3.5, esto como tal en un FileOutputStream es un poco complicado de escribir, porque en un FileOutputStream no puedes pasar sin más un Double, no tienes esa opción, sólo puedes pasar bytes o bytes. Sin embargo, la ventaja de un ObjectOutputStream es que podemos llamar a writeDouble(), proporcionarle ese doble y que se las apañe para mandárselo, que es lo que va a ocurrir cuando llamemos a writeDouble(). Este double se va a transformar en un valor binario, nos lo va a convertir a puro binario, y lo va a guardar como bytes dentro del FileOutputStream. Ya no tenemos que preocuparnos de hacer esos casteos, solo llamamos a writeBoolean() y guardar un true, o llamar a writeLong() y guardar un valor como 12345678901234L, y esto ya se ocupará de escribirlo como tenga que escribirlo. Lo transformará a binario simplemente para que se pueda guardar en el archivo llamando por debajo al FileOutputStream. Nos lo va a hacer por debajo el mismo.

Aquí lo he dejado un poco colocado: lo he metido en un try-with otra vez. Podemos meter múltiples secuencias de constructores en un mismo try-with si los separamos por puntos y comas dentro de los paréntesis. Lo que voy a hacer ahora es ejecutarlo para volcar sobre el archivo esa información. Lo que va a ocurrir es que se van a guardar los valores 3.5, true y 12345678901234L dentro de mi archivo, que en este caso se llamará objetos.txt. Si yo lo abro, no me va a mostrar nada, porque este archivo no se puede interpretar correctamente. No tiene letras. No os penséis que se ha guardado el número tal cual y el valor true tal cual, si lo trato de abrir con un editor de textos que sí que se atreva a enseñarlo, veremos que los números y booleanos se han convertido en valores binarios que a primera vista no tiene mucho sentido interpretar. Eso es porque esto está pensado para ser abierto exclusivamente con un ObjectInputStream una vez más.

Me voy a llevar esto a otra parte, como puede ser la función escribirObjeto(), para que no me moleste. Voy a hacer uno que sea similar, pero que se va a llamar leerObjeto().

Voy a utilizar el equivalente: un InputStream, para probar a leer el contenido de ese mismo archivo. Voy a poner otra vez mi InputStream de archivo para abrir archivos.txt, y luego voy a utilizar un ObjectInputStream, al cual voy a llamar ois, que es una clase que funciona exactamente igual que ObjectOutputStream pero en sentido inverso: nosotros le vamos a tener que proporcionar ese InputStream del cual queremos leer. Y cuando le pidamos al de fuera leer, va a leer del de debajo, va a interpretar esos valores que en un archivo de texto no tienen ningún tipo de sentido, y devolverlo de forma bonita. Por ejemplo, en este caso si le pongo un catch para que deje de protestar, apreciaremos que ois, en este caso, ObjectInputStream, y recuerdo que InputStream no vale, tiene que ser un ObjectInputStream, dispondrá de métodos que nos van a permitir leer valores que no sean de tipo byte, sino que vamos a poder leer dobles, flotantes, ints, longs... incluso strings, con readUTF(). De este modo, si yo trato de leer en orden, puedo hacer System.out.println de ois.readDouble(), que es justo lo primero que he escrito, porque hay que ir en orden. System.out.println de ois.readBoolean(), y System.out.println de ois.readLong(), siendo la idea leerlo en el mismo orden en el que fueron escritos.

Si ahora llamo a leerObjecto(), se va a abrir el archivo objetos.txt, se va a reprocesar como un ObjectInputStream y vamos a poder tener acceso a cada uno de los valores de los tipos correctos. En este caso, el resultado de ejecutarlo nos muestra que es así, se ha leído correctamente. Esto contrarresta con lo que pasaría si llamase a leer() y tratase de leer el archivo objetos.txt, esto no tendría mucho sentido porque estaría leyéndolos con forma de bytes y eso no sería interpretable.

Desplegar transcripción del episodio

[Música]

Otro tipo de OutputStream va a ser el ObjectOutputStream, que es un OutputStream envolvente. La idea de este OutputStream va a consistir en proporcionarle como parámetro otro OutputStream, y lo que va a ocurrir es que cuando llamemos al de fuera, se van a transformar las llamadas que hagamos a write(), y se va a llamar al write() de dentro, pero con opciones transformadas. Es una forma de decorarlo, de tal forma que lo que se guarde en el OutputStream de salida sea ligeramente diferente a lo que le hemos dicho al de fuera.

En este caso, yo le tengo que proporcionar uno, así que voy a copiar el que he estado usando antes de tipo FileOutputStream, aunque lo voy a guardar como objetos.txt para que sea distinto. Ahora, lo que voy a hacer es pedirle a ObjectOutputStream que guarde en éste. Tiene más sentido usar una variable de tipo ObjectOutputStream, porque así podemos tener acceso a los métodos extra de ObjectOutputStream que podemos usar a nuestra disposición como writeByte(), writeChar(), writeDouble()... y similares. Estos no forman parte de OutputStream, sólo forman parte de ObjectOutputStream. Es precisamente la razón por la que querremos instanciar un ObjectOutputStream en primer lugar.

El funcionamiento de esta clase va a consistir en que nosotros, en vez de escribir bytes, vamos a tener la opción de pasarle valores más coherentes como writeInt(), writeDouble(), writeChar()... así, podemos pasarle valores de distintos tipos. Por ejemplo, si tuviese un valor de tipo Float o Double, que fuese 3.5f o 3.5, esto como tal en un FileOutputStream es un poco complicado de escribir, porque en un FileOutputStream no puedes pasar sin más un Double, no tienes esa opción, sólo puedes pasar bytes o bytes. Sin embargo, la ventaja de un ObjectOutputStream es que podemos llamar a writeDouble(), proporcionarle ese doble y que se las apañe para mandárselo, que es lo que va a ocurrir cuando llamemos a writeDouble(). Este double se va a transformar en un valor binario, nos lo va a convertir a puro binario, y lo va a guardar como bytes dentro del FileOutputStream. Ya no tenemos que preocuparnos de hacer esos casteos, solo llamamos a writeBoolean() y guardar un true, o llamar a writeLong() y guardar un valor como 12345678901234L, y esto ya se ocupará de escribirlo como tenga que escribirlo. Lo transformará a binario simplemente para que se pueda guardar en el archivo llamando por debajo al FileOutputStream. Nos lo va a hacer por debajo el mismo.

Aquí lo he dejado un poco colocado: lo he metido en un try-with otra vez. Podemos meter múltiples secuencias de constructores en un mismo try-with si los separamos por puntos y comas dentro de los paréntesis. Lo que voy a hacer ahora es ejecutarlo para volcar sobre el archivo esa información. Lo que va a ocurrir es que se van a guardar los valores 3.5, true y 12345678901234L dentro de mi archivo, que en este caso se llamará objetos.txt. Si yo lo abro, no me va a mostrar nada, porque este archivo no se puede interpretar correctamente. No tiene letras. No os penséis que se ha guardado el número tal cual y el valor true tal cual, si lo trato de abrir con un editor de textos que sí que se atreva a enseñarlo, veremos que los números y booleanos se han convertido en valores binarios que a primera vista no tiene mucho sentido interpretar. Eso es porque esto está pensado para ser abierto exclusivamente con un ObjectInputStream una vez más.

Me voy a llevar esto a otra parte, como puede ser la función escribirObjeto(), para que no me moleste. Voy a hacer uno que sea similar, pero que se va a llamar leerObjeto().

Voy a utilizar el equivalente: un InputStream, para probar a leer el contenido de ese mismo archivo. Voy a poner otra vez mi InputStream de archivo para abrir archivos.txt, y luego voy a utilizar un ObjectInputStream, al cual voy a llamar ois, que es una clase que funciona exactamente igual que ObjectOutputStream pero en sentido inverso: nosotros le vamos a tener que proporcionar ese InputStream del cual queremos leer. Y cuando le pidamos al de fuera leer, va a leer del de debajo, va a interpretar esos valores que en un archivo de texto no tienen ningún tipo de sentido, y devolverlo de forma bonita. Por ejemplo, en este caso si le pongo un catch para que deje de protestar, apreciaremos que ois, en este caso, ObjectInputStream, y recuerdo que InputStream no vale, tiene que ser un ObjectInputStream, dispondrá de métodos que nos van a permitir leer valores que no sean de tipo byte, sino que vamos a poder leer dobles, flotantes, ints, longs... incluso strings, con readUTF(). De este modo, si yo trato de leer en orden, puedo hacer System.out.println de ois.readDouble(), que es justo lo primero que he escrito, porque hay que ir en orden. System.out.println de ois.readBoolean(), y System.out.println de ois.readLong(), siendo la idea leerlo en el mismo orden en el que fueron escritos.

Si ahora llamo a leerObjecto(), se va a abrir el archivo objetos.txt, se va a reprocesar como un ObjectInputStream y vamos a poder tener acceso a cada uno de los valores de los tipos correctos. En este caso, el resultado de ejecutarlo nos muestra que es así, se ha leído correctamente. Esto contrarresta con lo que pasaría si llamase a leer() y tratase de leer el archivo objetos.txt, esto no tendría mucho sentido porque estaría leyéndolos con forma de bytes y eso no sería interpretable.