Ein- und Ausgabe

Ein Strom ist verantwortlich für den Transport von Daten von einer Datenquelle zu einer Datensenke. Über einen Eingabestrom liest das Programm Daten ein, die Datenquelle kann jeweils verschieden sein. Einen Ausgabestrom benutzt das Programm dagegen zum Transport von Daten an einen äußeren Verbraucher.

Das JDK-1.1 unterscheidet zwischen Strömen für

Dies ist notwendig, da char-Werte  dem 16-bit Unicode-Zeichensatz angehören und im allgemeinen mehr als ein Byte benötigen. Im wesentlichen bieten  Reader/Writer Klassen allerdings die gleichen Methoden wie InputStream/OutputStream Klassen. Nur dort, wo Byte-Strom Methoden auf Bytes oder Byte-Feldern (byte, byte[]) operieren, arbeiten die entsprechenden Character-Strom-Methoden mit Zeichen, Zeichenfeldern und Zeichenketten (char, char[], String). Die Klassen InputStreamReader und OutputStreamWriter wandeln Character-Daten in Byte-Daten: InputStreamReader liest von einem InputStream und wandelt Byte-Daten in Character-Daten, ein OutputStreamWriter schreibt in einen OutputStream und wandelt dabei Character-Daten in Byte-Daten.

Weiterhin werden die Ströme nach der Art der Datenquellen/-senken unterschieden:

Die verschieden Verarbeitungsarten während des Datentransport sind die dritte Kategorie, nach der Ströme klassifiziert sind:
Reader Klassen
Writer Klasen
InputStream Klassen
OutputStream Klassen

Beispielprogramm zum Kopieren einer Datei.
 

Angewendetes Entwurfsmuster

Das Entwurfsmuster Dekorierer (durchsichtige Hülle, Wrapper) ist von den Entwicklern des java.io Paketes angewendet worden. Es lautet in Kurzform:
"Erweitere ein Objekt dynamisch um Zuständigkeiten. Dekorierer bieten eine flexible Alternative zur Unterklassenbildung, um die Funktionalität einer Klasse zu erweitern."
Das Muster unterscheidet zwischen konkreten und dekorierenden Objekten. Eine konkretes Objekt stellt eine unverzichtbare Basisoperation bereit, im Beispiel hier ist das ein Strom, der eine Schreib- oder Leseoperation durch direkten Zugriff auf die Datenquellen/-senken realisiert und damit zu einer der folgenden Klassen gehört:  FileInputStream, FileOutputStream, FileReader, FileWriter, StringReader, StringWriter, CharArrayReader, CharArrayWriter, ByteArrayInputStream, ByteArrayOutputStream, PipedInputStream, PipedOutputStream, PipedReader, PipedWriter.
Ein dekorierendes Objekt ergänzt ein anderes Objekt um eine bestimmte zusätzliche Operation oder einen weiteren Zustand. Dabei spielt es keine Rolle, ob das so dekorierte (umhüllte) Objekt konkret oder gar selbst dekorierend (umhüllend)  ist. Im Beipiel gehört ein dekorierendes (umhüllendes) Objekt zu einer der Klassen: BufferedReader, BufferedWriter, BufferedInputStream, BufferedOutputStream, FilterReader, FilterInputStream, FilterWriter, FilterOutputStream, InputStreamReader, OutputStreamWriter, ObjectInputStream, ObjectOutputStream, DataInputStream, DataOutputStream, SequenceInputStream, LineNumberReader, PushbackReader, PushbackInputStream, PrintWriter. Ein dekorierendes Objekt stellt die Basisoperation ebenfalls bereit, realisiert diese Operation aber nicht selbst, sondern beauftragt im allgemeinen das umhüllte Objekt mit der Erledigung dieser Operation, z.B. wird die Anforderung an einen PushbackReader, eine einfache Leseoperation auszuführen, an das umhüllte Objekt delegiert, das z.B. ein FileReader Objekt sein kann.

Durch das Dekorierer werden die einzelnen Verarbeitungsströme flexibel miteinander kombinierbar und zwar in einer Vielzahl von Möglichkeiten. Es wird vermieden, für jede dieser Möglichkeiten eine eigene Klasse zu definieren. Z.B in einem Parser wird meist ein Eingabestrom benötigt, der sowohl Zeilen zählen als auch ein Zeichen vorausschauen kann und die Zeichen aus einer Datei liest. Statt nun eine Klasse PushBackLineNumberFileReader in der Klassenbibliothek vorzusehen, wird ein derartiger Strom durch sukzessives, mehrmaliges Dekorieren (Umhüllen) eines konkreten Objekts zur Laufzeit erzeugt. Im Quelltext kann das so aussehen:

FileReader fr = new FileReader("ein.txt");
LineNumberReader lnr = new LineNumberReader(fr);
PushbackReader pbr = new PushbackReader(fr);
Innerhalb des Parsers können nun die benötigten Operationen angefordert werden:
...
int n = lnr.getLineNumber();
...
int c = fr.read();
    c = lnr.read();
..;
pbr.unread(c);
Die Operationen der umhüllten Objekte stehen nach wie vor zur Verfügung, wie z.B. dieMethode getLineNumber(). Daher spricht man hier auch von einer durchsichtigen Hülle.
Das Dekorierermuster bietet eine Alternative zur Vererbung, insbesondere in solchen Fällen, in denen eine Variantenexplosion droht.