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

Java IO

Object streams con Strings y Objects

• Duración: 6:08

Cómo usar ObjectOutputStream y ObjectInputStream para volcar cadenas de caracteres e instancias de clases arbitrarias como ArrayList es lo que se va a contar en este apartado.

Ya que estamos con ObjectOutputStream y ObjectInputStream, también los podemos usar de formas un poquito diferentes y podemos guardar cierta información que puede parecernos un poco peculiar. Por ejemplo, podemos guardar en principio UTFs, que son cadenas de caracteres. Para evitar cualquier tipo de compromiso, esto se guarda en formato UTF para asegurarse su máxima portabilidad. Si yo trato de escribir la cadena de caracteres hola mundo, se va a escribir literalmente la cadena de caracteres hola mundo, cosa que luego podemos probar si la tratamos de leer utilizando readUTF(), que funciona de forma exactamente idéntica. Primero voy a llamar a escribirObjeto(), porque me parece que si no, no se va a escribir, y luego os enseño qué va a ocurrir.

Si abro ahora el archivo objetos.txt, nos vamos a encontrar esa cadena de caracteres, ese hola mundo, y lo vamos a poder leer desde el bloc de notas porque casualmente los caracteres son los que son, y se ha escrito la cadena de caracteres. Pero en realidad, se habrá escrito algo más de información, así que no podremos usar este sistema para leer cadenas de caracteres usando readUTF(), debido a que, aunque nosotros no lo vemos, también se ha escrito un valor, que está en binario, que dice cuántas cadenas de caracteres tiene esta cadena. La forma de operar de readUTF() y de writeUTF() va a consistir en guardar el número de caracteres que se tienen que volcar y que componen esta cadena de caracteres. No nos vale si simplemente queremos volcar un string en un txt, por lo que sea, podemos guardar cosas como strings.

O como objetos, que es quizás el atractivo más importante que tiene ObjectOutputStream. ¿U os pensábais que ObjectOutputStream se llamaba así sólo porque podía leer dobles y booleanos? No, la gracia de ObjectOutputStream y de ObjectInputStream está en que nos va a permitir leer cualquier tipo de objeto que se pueda escribir. Ahora veremos, de todos modos, cómo crear objetos propios que se puedan escribir. Sin embargo, la mayoría de elementos de la biblioteca estandar de Java se pueden escribir como tal.

Por ejemplo, si fabrico un ArrayList de enteros, o de Strings, que se llame elementos, y sobre él trato de empujar valores como hola, adios, buenas... Ahora lo que puedo hacer es una llamada al método writeObject(), que está acá, y ahora le puedo proporcionar cualquier objeto. (Asterisco: luego veremos que no todos los objetos se pueden proporcionar). Es gracioso porque se llama writeObject y luego en letra pequeña una voz muy deprisa os lee: No se puede guardar cualquier tipo de objeto. Pero casi todos los de la biblioteca estandar de Java los vamos a poder guardar y aparte ahora vamos a ver cómo hacer nuestros propios objetos que se puedan dentro de un ObjectOutputStream. Y si yo se lo pido, adelante.

¿Qué habrá ocurrido ahora? Se habrá volcado tal cual el ArrayList dentro de mi archivo de texto. Si lo abro con el editor del sistema, para que se pueda ver desde un bloc de notas normal y corriente, vemos aquí cosas peculiares como que por ejemplo aparece java.util.ArrayList, vemos una palabra que dice size, aparece hola, adios, buenas... pero claro, está mezclado con un montón de ruido.

Esto ocurre porque cuando llamamos a writeObject(), lo que hace Java es, literalmente, guardar todos y cada unos de los campos que se pueden guardar y que conforman esa clase, en este caso ArrayList. Lo que aquí se ha guardado son las instrucciones para recomponer, cuando llamemos a readObject(), créate un ArrayList, ponle estos valores como parámetros, y luego recuerda que dentro tienes que volcarle el hola, adios y buenas dentro del ArrayList. Esta información no está pensada para que la consumamos nosotros, sino que está pensado para que la consuma Java cuando vayamos a recomponer ese objeto.

Por ejemplo, si ahora llamase a ois.readObject(), tendríamos la posibilidad de leer ese objeto. Y si nos fijamos, tira un error un poco especial que es ClassNotFoundException. ¿Esto por qué puede ocurrir? En el caso de java.util.ArrayList sería un poco raro que ocurriese, pero imaginad que estamos volcando clases propias o clases internas, y las leemos en un programa que no tiene todas las clases porque no es el mismo código fuente. Tenemos que tratar el error que debe ocurrir cuando, por la razón que sea, Java sea incapaz de recuperar el ArrayList o la clase que estemos intentando obtener. Le voy a añadir una cláusula adicional en mi try-catch, y lo que voy a hacer es guardar esto en mi ArrayList. Simple y llanamente. Esto lo va a leer como algo de tipo Object, así que también tendré que castearlo, es decir, ponerle entre paréntesis algo para que convierta el readObject() en un ArrayList. Asegurad que lo estáis leyendo correctamente porque si el tipo no cuadra y no puede castear, también se va a lanzar un error. Esto puede lanzar un error por cualquier cosa.

Ahora, si intento imprimir su longitud, que de hecho lo único que quiero es ponerle la línea de código aquí para poder poner un breakpoint, lo que va a ocurrir ahora es que cuando llame a leerObjeto() ese ArrayList se va a leer tal cual en bruto, y vamos a tener la posibilidad de ver su contenido. Aquí está nuestra lista, que tiene un size=3, eso ya anticipa resultados interesantes. Dentro vemos las tres cadenas de caracteres que hemos volcado. En principio, esto está funcionando. Podemos utilizar ObjectOutputStream y ObjectInputStream también para voclar tal cual clases arbitrarias que tengamos interés en guardar.

Esto no sale gratis, y tenemos que tener en cuenta una serie de normas que os voy a contar ahora.

Desplegar transcripción del episodio

[Música]

Ya que estamos con ObjectOutputStream y ObjectInputStream, también los podemos usar de formas un poquito diferentes y podemos guardar cierta información que puede parecernos un poco peculiar. Por ejemplo, podemos guardar en principio UTFs, que son cadenas de caracteres. Para evitar cualquier tipo de compromiso, esto se guarda en formato UTF para asegurarse su máxima portabilidad. Si yo trato de escribir la cadena de caracteres hola mundo, se va a escribir literalmente la cadena de caracteres hola mundo, cosa que luego podemos probar si la tratamos de leer utilizando readUTF(), que funciona de forma exactamente idéntica. Primero voy a llamar a escribirObjeto(), porque me parece que si no, no se va a escribir, y luego os enseño qué va a ocurrir.

Si abro ahora el archivo objetos.txt, nos vamos a encontrar esa cadena de caracteres, ese hola mundo, y lo vamos a poder leer desde el bloc de notas porque casualmente los caracteres son los que son, y se ha escrito la cadena de caracteres. Pero en realidad, se habrá escrito algo más de información, así que no podremos usar este sistema para leer cadenas de caracteres usando readUTF(), debido a que, aunque nosotros no lo vemos, también se ha escrito un valor, que está en binario, que dice cuántas cadenas de caracteres tiene esta cadena. La forma de operar de readUTF() y de writeUTF() va a consistir en guardar el número de caracteres que se tienen que volcar y que componen esta cadena de caracteres. No nos vale si simplemente queremos volcar un string en un txt, por lo que sea, podemos guardar cosas como strings.

O como objetos, que es quizás el atractivo más importante que tiene ObjectOutputStream. ¿U os pensábais que ObjectOutputStream se llamaba así sólo porque podía leer dobles y booleanos? No, la gracia de ObjectOutputStream y de ObjectInputStream está en que nos va a permitir leer cualquier tipo de objeto que se pueda escribir. Ahora veremos, de todos modos, cómo crear objetos propios que se puedan escribir. Sin embargo, la mayoría de elementos de la biblioteca estandar de Java se pueden escribir como tal.

Por ejemplo, si fabrico un ArrayList de enteros, o de Strings, que se llame elementos, y sobre él trato de empujar valores como hola, adios, buenas... Ahora lo que puedo hacer es una llamada al método writeObject(), que está acá, y ahora le puedo proporcionar cualquier objeto. (Asterisco: luego veremos que no todos los objetos se pueden proporcionar). Es gracioso porque se llama writeObject y luego en letra pequeña una voz muy deprisa os lee: No se puede guardar cualquier tipo de objeto. Pero casi todos los de la biblioteca estandar de Java los vamos a poder guardar y aparte ahora vamos a ver cómo hacer nuestros propios objetos que se puedan dentro de un ObjectOutputStream. Y si yo se lo pido, adelante.

¿Qué habrá ocurrido ahora? Se habrá volcado tal cual el ArrayList dentro de mi archivo de texto. Si lo abro con el editor del sistema, para que se pueda ver desde un bloc de notas normal y corriente, vemos aquí cosas peculiares como que por ejemplo aparece java.util.ArrayList, vemos una palabra que dice size, aparece hola, adios, buenas... pero claro, está mezclado con un montón de ruido.

Esto ocurre porque cuando llamamos a writeObject(), lo que hace Java es, literalmente, guardar todos y cada unos de los campos que se pueden guardar y que conforman esa clase, en este caso ArrayList. Lo que aquí se ha guardado son las instrucciones para recomponer, cuando llamemos a readObject(), créate un ArrayList, ponle estos valores como parámetros, y luego recuerda que dentro tienes que volcarle el hola, adios y buenas dentro del ArrayList. Esta información no está pensada para que la consumamos nosotros, sino que está pensado para que la consuma Java cuando vayamos a recomponer ese objeto.

Por ejemplo, si ahora llamase a ois.readObject(), tendríamos la posibilidad de leer ese objeto. Y si nos fijamos, tira un error un poco especial que es ClassNotFoundException. ¿Esto por qué puede ocurrir? En el caso de java.util.ArrayList sería un poco raro que ocurriese, pero imaginad que estamos volcando clases propias o clases internas, y las leemos en un programa que no tiene todas las clases porque no es el mismo código fuente. Tenemos que tratar el error que debe ocurrir cuando, por la razón que sea, Java sea incapaz de recuperar el ArrayList o la clase que estemos intentando obtener. Le voy a añadir una cláusula adicional en mi try-catch, y lo que voy a hacer es guardar esto en mi ArrayList. Simple y llanamente. Esto lo va a leer como algo de tipo Object, así que también tendré que castearlo, es decir, ponerle entre paréntesis algo para que convierta el readObject() en un ArrayList. Asegurad que lo estáis leyendo correctamente porque si el tipo no cuadra y no puede castear, también se va a lanzar un error. Esto puede lanzar un error por cualquier cosa.

Ahora, si intento imprimir su longitud, que de hecho lo único que quiero es ponerle la línea de código aquí para poder poner un breakpoint, lo que va a ocurrir ahora es que cuando llame a leerObjeto() ese ArrayList se va a leer tal cual en bruto, y vamos a tener la posibilidad de ver su contenido. Aquí está nuestra lista, que tiene un size=3, eso ya anticipa resultados interesantes. Dentro vemos las tres cadenas de caracteres que hemos volcado. En principio, esto está funcionando. Podemos utilizar ObjectOutputStream y ObjectInputStream también para voclar tal cual clases arbitrarias que tengamos interés en guardar.

Esto no sale gratis, y tenemos que tener en cuenta una serie de normas que os voy a contar ahora.