Leer y escribir en Java: InputStreams, OutputStreams, Readers y Writers

En Java todo es orientado a objetos, leer y escribir entonces no podía estar implementado de otra manera. Programación a objetos implica pensar "qué tenemos?". Tenemos algo. Algos. Cosas. Entidades de las que puedo leer. Entidades de las que puedo escribir. Para representar a "algo a lo que puedo escribir bytes", crearon entonces una simple clase abstracta:

abstract class OutputSream
{
	void write(int unByte);
	void close();
}

Y para representar a "algo

abstract class InputStream
{
	int read();
	void close();
}

Claro que también tienen esas clases métodos para leer y escribir más de un byte, pero la base es esa.

Sin necesidad de traspasar esa abstracción, un método puede recibir un "stream" y leer de él, sin saber si lee de la red, de un archivo, etc. Y siguiendo esta idea, las mismas implementaciones de streams se dedicaron a leer o a escrbir, abstractamente, de otros streams. Imaginemos una clase que implementa InputStream. Es algo de lo que puedo leer. Pero supongamos que exige ser construida a partir de otro InputStream, y en la implementación de su método read(), llama al que le habían pasado, pero cuenta los bytes. Ejemplo:

class ContadorInputStream
{
	final InputStream otro;
	int contador = 0;
	
	ContadorInputStream(InputSream o) { otro = o; }
	
	int read()
	{
		contador++;
		return otro.read();
	}
}

Supongamos que un programa leía de la red, y para eso manipulaba un objeto InputStream. Ahora queremos contar los bytes que lee, pero en vez meter por todos lados un contador, en cada llamada a read... hacemos otra cosa: le remplazamos su stream "a" por new ContadorInputStream(a). Como los dos son InputStream, el resto del programa ni entera de que le metieron algo en el medio!

Supongamos que además queremos desenctriptar eso que leíamos de la red. Y la desencriptación es simplemente aplicar la operación XOR (^) contra el valor 23. Sería así:

class DescifradorInputStream
{
	final InputStream otro;
	int contador = 0;
	
	ContadorInputStream(InputSream o) { otro = o; }
	
	int read()
	{
		return otro.read() ^ 23;
	}
}

Ahora, el programa, en "a", el programa va a tener new DescifradorInputStream(new ContadorInputStream(a)). Pensemos en lo que pasará cuando el programa ejecute, inocentemente, a.read().

  1. Se llama el read del objeto que es a, es decir DescifradorInputStream.
  2. En ese read se ejecuta otro.read(), que es el que se pasó en su constructor: ContadorInputStream.
  3. Se ejecuta el read de ContadorInputStream. Que llama al read del objeto original, que finalmente traerá un byte de la red.

A este estilo de encapsular una clase en otra, aprovechando que todas comparten una misma interfaz, se lo conoce como "Decorator" (o Decorador).

¡Continuará!

Vea mis otros artículos sobre programación en Java.


Me encantaría recibir cualquier tipo de comentarios sobre esta página. ¿Qué pongo? ¿Qué saco? Hecho por Nicolás Lichtmaier.

HTML 4.01 estricto válido