12.8 Die Unterklassen von Writer
 
Alle Klassen, die Unicode-Zeichen schreiben, basieren auf der abstrakten Klasse Writer. Aus Writer leiten sich später Klassen ab, die konkrete Ausgabegeräte ansprechen oder Daten filtern. Die folgende Tabelle zeigt die aus Writer abgeleiteten Klassen:
Tabelle 12.3
Übersicht der von Writer direkt abgeleiteten Klassen
Klasse
|
Bedeutung
|
OutputStreamWriter
|
Basisklasse für alle Writer, die einen Zeichen-Stream in einen Byte-Stream umwandeln. Auch Basisklasse für FileWriter.
|
PrintWriter
|
Konvertierung der primitiven Datentypen in Strings.
|
BufferedWriter
|
Writer, der puffert.
|
StringWriter
|
Writer, der in einen String schreibt.
|
CharArrayWriter
|
Writer, der in ein Zeichenfeld schreibt.
|
PipedWriter
|
Writer zur Ausgabe in einen passenden PipedReader.
|
FilterWriter
|
Abstrakte Basisklasse für Filterobjekte. Fast alle Writer sind Filter und somit Unterklasse von FilterWriter. Das gilt etwa für BufferedWriter, CharArrayWriter, FilterWriter, OutputStreamWriter, PipedWriter, PrintWriter, StringWriter.
|
12.8.1 Die abstrakte Basisklasse Writer
 
Basis für alle wichtigen Klassen ist die abstrakte Basisklasse Writer.
abstract class java.io. Writer
implements Appendable, Closeable, Flushable
|
|
protected Writer( Object lock )
Erzeugt einen Writer-Stream, der sich mit dem übergebenen Synchronisations-Objekt initialisiert. Ist die Referenz null, so gibt es eine NullPointerException. |
|
protected Writer()
Erzeugt einen Writer-Stream, der sich selbst als Synchronisations-Objekt nutzt. Der Konstruktor ist für die Unterklassen interessant, die kein eigenes Lock-Objekt zuordnen wollen. |
|
void write( int c ) throws IOException
Schreibt ein einzelnes Zeichen. Von der 32-Bit-Ganzzahl wird der niedrige Teil (16 Bit des ints) geschrieben. |
|
void write( char[] cbuf ) throws IOException
Schreibt ein Feld von Zeichen. |
|
abstract void write( char[] cbuf, int off, int len ) throws IOException
Schreibt len Zeichen des Felds cbuf ab der Position off. |
|
void write( String str ) throws IOException
Schreibt einen String. |
|
void write( String str, int off, int len ) throws IOException
Schreibt len Zeichen der Zeichenkette str ab der Position off. |
|
Writer append( char c ) throws IOException
Hängt ein Zeichen an. Verhält sich wie write(c), nur liefert es wie die Schnittstelle Appendable verlangt, ein Appendable zurück. Writer ist ein passendes Appendable. |
|
Writer append( CharSequence csq ) throws IOException
Hängt eine Zeichenfolge an. Auch aus der Schnittstelle Appendable. |
|
abstract void flush() throws IOException
Schreibt den internen Puffer. Hängt verschiedene flush()-Aufrufe in einer Kette zusammen, die sich aus der Abhängigkeit der Objekte ergibt. So werden alle Puffer geschrieben. Aus der Schnittstelle Flushable. |
|
abstract void close() throws IOException
Schreibt den gepufferten Strom und schließt ihn. Nach dem Schließen durchgeführte write()- oder flush()-Aufrufe bringen eine IOException mit sich. Ein zusätzliches close() wirft keine Exception. Aus der Schnittstelle Closeable. |
Wie die abstrakten Methoden genutzt und überschrieben werden
Uns fällt auf, dass von den sieben Methoden lediglich flush(), close() und write(char[], int, int) abstrakt sind. Zum einen bedeutet dies, dass konkrete Unterklassen nur diese Methoden implementieren müssen, und zum anderen, dass die übrigen write()-Funktionen auf die eine überschriebene Implementierung zurückgreifen. Werfen wir daher ein Blick auf die Nutznießer:
public void write(int c) throws IOException
{
synchronized (lock) {
if (writeBuffer == null)
writeBuffer = new char[writeBufferSize];
writeBuffer[0] = (char) c;
write(writeBuffer, 0, 1);
}
}
Wird ein Zeichen geschrieben, so wird zunächst einmal nachgesehen, ob schon früher ein temporärer Puffer eingerichtet wurde. (Ein schöner Trick, denn Speicherbeschaffung ist nicht ganz billig.) Wenn nicht, dann erzeugt die Funktion zunächst ein Array mit der Größe von 1 024 Zeichen. (Dies ist die eingestellte Puffer-Größe.) Dann schreibt write(int) das Zeichen in den Puffer und ruft die abstrakte Methode auf – die ja in einer Unterklasse implementiert wird. Ist der Parameter ein Feld, so muss lediglich die Größe an die abstrakte Methode übergeben werden. Alle Schreiboperationen sind mit einem lock-Objekt synchronisiert und können sich demnach nicht in die Quere kommen. Die Synchronisation wird entweder durch ein eigenes lock-Objekt durchgeführt, das dann im Konstruktor angegeben werden muss, oder die Klasse verwendet das this-Objekt der Writer-Klasse als Sperr-Objekt.
Schreiben einer Zeichenkette
Um einen Teil einer Zeichenkette zu schreiben, wird schon etwas mehr Aufwand betrieben. Ist der interne Puffer zu klein, wird ein neuer angelegt. Geschickt wird dieser Puffer cbuf gleich im Objekt gehalten, damit auch andere Funktionsaufrufe davon profitieren können. So wird vorgebeugt, dass vielleicht große Blöcke temporären Speichers verwendet werden.
public void write(String str, int off, int len) throws IOException
{
synchronized (lock) {
char cbuf[];
if (len <= writeBufferSize) {
if (writeBuffer == null)
writeBuffer = new char[writeBufferSize];
cbuf = writeBuffer;
} else
cbuf = new char[len];
str.getChars(off, (off + len), cbuf, 0);
write(cbuf, 0, len);
}
}
Wir sehen bei der letzten übrig gebliebenen Funktion, dass sie write(String) in schlauer Weise nutzt.
public void write(String str) throws IOException {
write(str, 0, str.length());
}
Es liegt nun an den Unterklassen, diese Methoden zu überschreiben. Wir haben gesehen, dass lediglich die eine abstrakte Methode zwingend überschrieben werden muss, doch ist es durchaus sinnvoll, etwa zugunsten einer höheren Geschwindigkeit, auch die anderen Funktionen zu überschreiben.
12.8.2 Ausgabemöglichkeiten durch PrintWriter erweitern
 
Bisher waren die Ausgabemöglichkeiten eines Writer-Objekts beschränkt. Die Möglichkeit, mit write() Zeichen oder Zeichenfelder auszugeben, ist im Programmieralltag nicht gerade ideal.
PrintWriter erweitert die Klasse Writer und bietet Methoden zur String-Konvertierung für alle primitiven Datentypen und für den Objekttyp. Dafür bietet der PrintWriter eine Reihe überladener Methoden namens print(), println(), printf() und format() an. (PrintWriter ist also die Writer-Variante von PrintStream.)
Beispiel Schreibe eine Zeichenkette in eine Datei.
PrintWriter out = new PrintWriter( "c:\datei.txt" );
out.println( "Hallo Welt" );
out.close();
Mit diesem Konstruktor wird der PrintWriter nicht als Dekorator um einen existierenden Writer benötigt; es muss also kein FileWriter in den Konstruktor von PrintWriter gegeben werden.
|
Die Konstruktoren erwarten entweder ein File, OutputStream, Writer oder Dateinamen. Die Ausgabe in den PrintWriter ist so lange gepuffert, bis flush() ausgeführt wird. Das Verhalten lässt sich mit einem Schalter ändern – jeder Konstruktor existiert in zwei Varianten: ist der Wahrheitsparameter autoflush mit true belegt, wird nach einem Zeilenumbruch automatisch flush() aufgerufen. So leert etwa println()den Puffer, wenn der Konstruktor mit autoFlush gleich true aufgerufen wurde.
class java.io. PrintWriter
extends Writer
|
|
PrintWriter( Writer out ) |
|
PrintWriter( OutputStream out ) |
|
PrintWriter( File file ) |
|
PrintWriter( String fileName )
Erzeugt einen neuen PrintWriter, der in die angegebene Datei – entweder durch den Dateinamen oder File-Objekt gegeben –, den Writer bzw. OutputStream schreibt. Bei einem Zeilenende wird nicht automatisch den Puffer geschrieben. |
|
PrintWriter( Writer out, boolean autoFlush ) |
|
PrintWriter( OutputStream out, boolean autoFlush )
Erzeugt einen neuen PrintWriter, der automatisch bei autoFlush gleich true am Zeilenende mittels println() den Puffer schreibt. |
|
PrintWriter( File file, String csn ) |
|
PrintWriter( String fileName, String csn )
Erzeugt einen neuen PrintWriter zum Schreiben in Dateien mit den angegeben Kodierungen. |
|
boolean checkError()
Schreibt die gepufferten Daten und prüft Fehler. Die Abfrage ist wichtig, da die Klasse keine Ein-/Ausgabe-Exceptions auswirft. |
|
void close()
Schließt den Strom. |
|
void flush()
Schreibt gepufferte Daten. |
|
void print( boolean|char|char[]|double|float|int|Object|String )
Schreibt Boolean oder Zeichen, Array von Zeichen, Fließkommazahlen, Ganzzahlen, Object oder String. |
|
void println()
Schreibt Zeilenvorschubzeichen. |
|
void println( boolean|char|char[]|double|float|int|Object|String )
Schreibt den Datentyp wie print() und schließt die Zeile mit Zeilenendezeichen ab. |
|
PrintWriter format( Locale l, String format, Object... args ) |
|
PrintWriter format( String format, Object... args ) |
|
PrintWriter printf( Locale l, String format, Object... args ) |
|
PrintWriter printf( String format, Object... args )
Gibt eine nach dem Formatierungsstring gegebene Zeichenfolge aus. |
|
void setError()
Zeigt an, dass ein Fehler auftrat. |
|
PrintWriter append( char c ) |
|
PrintWriter append( CharSequence csq ) |
|
PrintWriter append( CharSequence csq, int start, int end )
Schreibt Zeichen, Felder von Zeichen oder String. Die append()-Methoden stammen aus der Schnittstelle Appendable. |
|
void write( char[] buf ) |
|
void write( char[] buf, int off, int len ) |
|
void write( int c ) |
|
void write( String s ) |
|
void write( String s, int off, int len )
Schreibt einzelne Zeichen, Felder von Zeichen oder String bzw. Teile von Arrays oder Strings. Die fünf Methoden überschreiben Methoden der Basisklasse Writer. |
Keine der Methoden wirft eine IOException. Intern fängt der PrintWriter eine mögliche Exception ab und setzt ein internes Flag trouble, das auf das Programm jedoch keinen weiteren Einfluss hat. Daher musste auch die Methode write(String) neu definiert werden, da die Funktion write(String) der Writer-Klasse eine IOException wirft.
12.8.3 Datenkonvertierung durch den OutputStreamWriter
 
Die Klasse OutputStreamWriter ist sehr interessant, da sie Konvertierungen der Zeichen nach einer Zeichenkodierung vornimmt. So wird sie, unterstützt durch die einzige Unterklasse FileWriter, für Ausgaben in Dateien noch wichtiger. Jeder OutputStreamWriter konvertiert auf diese Weise Zeichenströme von einer Zeichenkodierung (etwa EBCDIC) in die andere (etwa Latin –1). Die Zeichenkodierung kann im Konstruktor eines OutputStreamWriter-Objekts angegeben werden. Ohne Angabe ist es der Standardkonvertierer, der in den Systemeigenschaften unter dem Schlüssel file.encoding geschrieben ist. Die Kodierung der Zeichen wird über einen StreamEncoder vorgenommen. Die Klasse liegt unter dem Paket sun.nio.cs.
class java.io. OutputStreamWriter
extends Writer
|
|
OutputStreamWriter( OutputStream out )
Erzeugt einen OutputStreamWriter, der die Standardkodierung verwendet. |
|
OutputStreamWriter( OutputStream out, Charset|CharsetEncoder cs )
Erzeugt einen OutputStreamWriter mit einem Charset oder einem CharsetEncoder. |
|
OutputStreamWriter( OutputStream out, String enc )
Erzeugt einen OutputStreamWriter mit der vorgegebenen Kodierung. |
|
void close()
Schließt den Datenstrom. |
|
void flush()
Schreibt den gepufferten Strom. |
|
String getEncoding()
Liefert die Kodierung des Datenstroms als String. |
|
void write( char[] cbuf, int off, int len )
Schreibt Zeichen des Felds. |
|
void write( int c )
Schreibt ein einzelnes Zeichen. |
|
void write( String str, int off, int len )
Schreibt den Teil eines Strings. |
12.8.4 In Dateien schreiben mit der Klasse FileWriter
 
Der FileWriter ist ein spezieller Writer, der Ausgaben in eine Datei erlaubt. Methoden aus Oberklassen werden nicht überschreiben bzw. implementiert, und so fügt die Klasse nur Konstruktoren hinzu, damit eine Datei geöffnet werden kann.
Nachfolgendes Programm erstellt die Datei fileWriter.txt und schreibt eine Textzeile hinein. Da der Konstruktor und die write()-Methoden eine IOException in dem Fall auswerfen, wenn ein Öffnen nicht möglich ist, müssen wir einen try/catch-Block um die Anweisungen setzen oder mit throws den Fehler noch oben weitergeben.
Listing 12.14
FileWriterDemo.java
import java.io.*;
public class FileWriterDemo
{
public static void main( String[] args )
{
Writer fw = null;
try
{
fw = new FileWriter( "fileWriter.txt" );
fw.write( "Hallo Welt geht in eine Datei" );
}
catch ( IOException e ) {
System.err.println( "Konnte Datei nicht erstellen" );
}
finally {
if ( fw != null )
try { fw.close(); } catch ( IOException e ) { e.printStackTrace(); }
}
}
}
class java.io. FileWriter
extends OutputStreamWriter
|
|
FileWriter( File file ) |
|
FileWriter( FileDescriptor fd ) |
|
FileWriter( String filename )
Erzeugt einen Ausgabestrom zum Schreiben in eine Datei. |
|
FileWriter( File file, boolean append ) |
|
FileWriter( String filename, boolean append )
Erzeugt einen Ausgabestrom, hängt die Daten an eine existierende Datei an. |
FileWriter, OutputStreamWriter und FileOutputStream
OutputStreamWriter ist die Basisklasse für die konkrete Klasse FileWriter und ist für die Konvertierung der Zeichen in Bytefolgen verantwortlich. Die Konstruktoren bauen in FileOutputStream-Objekt auf und füttern damit den Konstruktor von OutputStreamWriter. Die write()-Methoden vom OutputStreamWriter konvertieren die Zeichen in Byte, die letztendlich der FileOutputStream schreibt.
public class FileWriter extends OutputStreamWriter
{
public FileWriter(String fileName) throws IOException {
super(new FileOutputStream(fileName));
}
public FileWriter(String fileName, boolean append)
throws IOException {
super(new FileOutputStream(fileName, append));
}
...
}
Mit diesen Konstruktoren kann nun eine Datei geöffnet werden. Der einfachste Weg ist der über den Dateinamen. Existiert die Datei, deren Namen wir übergeben, bereits, wird die Datei gelöscht. Um die Daten hinten anzuhängen, übergeben wir als zweites Argument true. Eine weitere Möglichkeit, Daten hinten anzuhängen, bietet die Klasse RandomAccessFile oder FileOutputStream.
12.8.5 StringWriter und CharArrayWriter
 
Zwei interessante Klassen sind StringWriter und CharArrayWriter. Sie sind ebenfalls von Writer abgeleitet, schreiben jedoch die Ausgabe nicht in eine Datei, sondern in einen StringBuffer beziehungsweise in ein Zeichen-Array. Die Felder werden automatisch vergrößert.
StringWriter
In den folgenden Programmzeilen konvertieren wir den StringWriter noch zu einem PrintWriter, damit wir die komfortable println()-Methode verwenden können.
StringWriter buffer = new StringWriter();
PrintWriter out = new PrintWriter( buffer );
out.println( "Ulli ist lieb" );
out.println( "Quatsch" );
String result = buffer.toString();
Hier findet der parameterlose Konstruktor Verwendung. Er legt einen StringWriter mit der Größe 16 an (Standardgröße eines StringBuffer-Objekts). Daneben existiert aber noch ein Konstruktor mit dem Parameter int. Dieser legt dann die anfängliche Größe fest. Wenn unser StringWriter größer als 16 wird, und das ist wahrscheinlich, sollte immer der zweite Konstruktor aus Performanzgründen Verwendung finden. So muss sich das interne StringBuffer-Objekt bei wiederholten write()-Aufrufen nicht immer in der Größe ändern. Mit den Funktionen getBuffer() und toString() lesen wir den Inhalt wieder aus. Die Methoden unterscheiden sich darin, dass getBuffer() ein StringBuffer-Objekt zurückgibt und toString() das gewohnte String-Objekt.
class java.io. StringWriter
extends Writer
|
|
StringWriter()
Erzeugt einen StringWriter mit der Startgröße 16. |
|
StringWriter( int initialSize )
Erzeugt einen StringWriter mit der angegebenen Größe. |
|
void close()
Schließt den StringWriter. Dennoch lassen sich nach dem Schließen Zeichen schreiben, da die close()-Methode leer implementiert ist. |
|
void flush()
Schreibt die gepufferten Daten in den Stream; leer implementiert. |
|
StringBuffer getBuffer()
Liefert den internen StringBuffer, keine Kopie. |
|
String toString()
Liefert den Puffer als String. |
|
void write( char[] cbuf, int off, int len )
Schreibt einen Teil der Zeichen des Felds. |
|
void write( int c ), StringWriter append( char c )
Schreibt ein einzelnes Zeichen. |
|
void write( String str ), StringWriter append( CharSequence seq )
Schreibt eine Zeichenfolge. |
|
void write( String str, int off, int len )
Schreibt den Teil eines Strings. |
CharArrayWriter
Neben StringWriter schreibt auch die Klasse CharArrayWriter Zeichen in einen Puffer, doch diesmal in ein Zeichenfeld. Sie bietet zudem drei zusätzliche Funktionen an: reset(), size() und writeTo().
class java.io. CharArrayWriter
extends Writer
|
|
CharArrayWriter()
Erzeugt einen neuen CharArrayWriter. |
|
CharArrayWriter( int initialSize )
Erzeugt einen neuen CharArrayWriter mit einer Standardgröße. |
|
void write( int c ), CharArrayWriter append( char c )
Schreibt ein Zeichen in den Puffer. |
|
CharArrayWriter append( CharSequence seq )
Schreibt eine Zeichenfolge. |
|
void write( char[] c, int off, int len )
Schreibt ausgewählte Zeichen in den Puffer. |
|
void write( String str, int off, int len )
Schreibt einen Teil eines String in den Puffer. |
|
void writeTo( Writer out )
Schreibt den Inhalt des Puffers in einen anderen Zeichenstrom. Diese Methode ist recht nützlich, um die Daten weiterzugeben. |
|
void reset()
Setzt den internen Puffer zurück, sodass das CharArrayWriter-Objekt ohne neue Speicheranforderung genutzt werden kann. |
|
int size()
Liefert die Größe des Puffers. |
|
char[] toCharArray()
Gibt eine Kopie der Eingabedaten zurück. Es ist wirklich eine Kopie und keine Referenz. |
|
String toString()
Konvertiert die Eingabedaten in einen String. |
|
void close()
Schließt den Stream. |
|
void flush()
Leert den Stream. |
12.8.6 Writer als Filter verketten
 
Die Funktionalität der bisher vorgestellten Writer-Klassen reicht für den Alltag zwar aus, doch sind Ergänzungen gefordert, die den Nutzen oder die Fähigkeiten der Klassen erweitern. In Java gibt es die drei Klassen BufferedWriter, PrintWriter und FilterWriter, die einen Writer im Konstruktor erlauben und ihre Ausgabe an diesen weiterleiten. Die neue Klasse erweitert die Funktionalität, schreibt ihre Ausgabe in den alten Writer und die Klassen werden somit geschachtelt. Es lassen sich auch alle drei Klassen allesamt ineinander verschachteln. Das ist das Design-Pattern Dekorator.
12.8.7 Gepufferte Ausgabe durch BufferedWriter
 
Die Klasse BufferedWriter hat die Aufgabe, Dateiausgaben, die mittels write() in den Stream geleitet werden, zu puffern. Dies ist immer dann nützlich, wenn viele Schreiboperationen gemacht werden, denn das Puffern macht die Dateioperationen wesentlich schneller, da so mehrere Schreiboperationen zu einer zusammengefasst werden. Um die Funktionalität eines Puffers zu erhalten, enthält ein BufferedWriter-Objekt einen internen Puffer, in dem die Ausgaben von write() zwischengespeichert werden. Standardmäßig ist dieser Puffer 8 192 Zeichen groß. Er kann aber über einen parametrisierten Konstruktor auf einen anderen Wert gesetzt werden. Erst wenn der Puffer voll ist oder die Methoden flush() oder close() aufgerufen werden, werden die gepufferten Ausgaben geschrieben. Durch die Verringerung tatsächlicher write()-Aufrufe an das externe Gerät wird die Geschwindigkeit der Anwendung deutlich erhöht.
 Hier klicken, um das Bild zu Vergrößern
Um einen BufferedWriter anzulegen, gibt es zwei Konstruktoren, denen ein bereits existierender Writer übergeben wird. An diesen Writer wird dann der Filter seinerseits die Ausgaben weiterleiten, insbesondere nach einem Auruf von flush(), close() oder einem internen Überlauf.
Zusätzlich bietet die Klasse die Methode writeLine(), die in der Ausgabe eine neue Zeile beginnt. Das Zeichen für den Zeilenwechsel wird aus der Systemeigenschaft line.separator genommen. Da sie intern mit der write()-Methode arbeitet, kann sie eine IOException auslösen.
Beispiel Wir bauen zunächst einen FileWriter. Dieser sichert die Daten, die mittels write() gesendet werden, in einer Datei. Anschließend erzeugen wir einen BufferedWriter, der die Daten, die in die Datei geschrieben werden, erst einmal sammelt. Diesen BufferedWriter erweitern wir zu einem PrintWriter, da ein PrintWriter neue Schreibfunktionen besitzt, sodass wir nicht mehr nur auf write()-Methoden angewiesen sind, sondern die komfortablen print()-Funktionen nutzen können
|
Listing 12.15
ChainedWriter.java
import java.io.*;
public class ChainedWriter
{
public static void main( String[] args )
{
PrintWriter pw = null;
try
{
Writer fw = new FileWriter( "charArrayWriterDemoPuffer.txt" );
Writer bw = new BufferedWriter( fw );
pw = new PrintWriter( bw );
for ( int i = 1; i < 10000; i++ )
pw.println( "Zeile " + i );
}
catch ( IOException e ) {
System.out.println( "Konnte Datei nicht erstellen" );
}
finally {
if ( pw != null )
pw.close();
}
}
}
|
Tipp Ein Geschwindigkeitstipp noch zum Schluss. Falls bekannt, sollte die Puffergröße des BufferedWriter (Gleiches gilt für BufferedReader) mit der internen Puffergröße des Betriebssystems übereinstimmen. Hier können Geschwindigkeitsmessungen mit unterschiedlichen Puffergrößen die Lösung bringen.
|
class java.io. BufferedWriter
extends Writer
|
|
BufferedWriter( Writer out )
Erzeugt einen puffernden BufferedWriter mit der Puffergröße 8 k. |
|
BufferedWriter( Writer out, int sz )
Erzeugt einen puffernden BufferedWriter mit der Puffergröße sz, die nicht kleiner 0 sein darf. Andernfalls gibt es eine IllegalArgumentException. Die Puffergröße 0 ist erlaubt, aber unsinnig. |
Alle write(), append()-Funktionen sind so implementiert, dass sie erst im Puffer landen. Wenn der Puffer voll ist, werden die an den im Konstruktor übergebenen Writer durchgespült.
Ein nettes Detail am Rande bietet die Implementierung von BufferedWriter. Wir finden hier die Deklaration der Methode min(), die in den beiden write()-Methoden für Teil-strings beziehungsweise Teilfelder Verwendung findet.
/**
* Our own little min method, to avoid loading java.lang.Math if we’ve run
* out of file descriptors and we’re trying to print a stack trace.
*/
private int min(int a, int b) {
if (a < b) return a;
return b;
}
12.8.8 Daten mit FilterWriter filtern
 
Die Architektur der Java-Klassen macht es leicht, eigene Filter zu programmieren. Basis ist dafür die abstrakte Klasse FilterWriter. Wir übergeben im Konstruktor ein Writer-Objekt, an das die späteren Ausgaben weitergeleitet werden. Das Konstruktor-Argument wird in dem protected-Attribut out des FilterWriter-Objektes gesichert. In der Unterklasse greifen wir darauf zurück, denn dort schickt der Filter seine Ausgaben hin.
Die Standardimplementierung der Klasse FilterWriter überschreibt drei der write()-Methoden so, dass die Ausgaben an den im Konstruktor übergebenen Writer gehen.
abstract class java.io. FilterWriter
extends Writer
|
|
protected Writer out
Der Ausgabestrom, an den die Daten geschickt werden. Im Konstruktor gesetzt. |
|
protected FilterWriter( Writer out )
Erzeugt einen neuen filternden Writer. |
|
void write( int c )
Schreibt ein einzelnes Zeichen. |
|
void write( char[] cbuf, int off, int len )
Schreibt einen Teil eines Zeichenfelds. |
|
void write( String str, int off, int len )
Schreibt einen Teil eines Strings. |
|
void close()
Schließt den Stream. |
|
void flush()
Leert den internen Puffer des Streams. |
Die Klasse ist abstrakt, also können keine direkten Objekte erzeugt werden. Dennoch gibt es einen protected-Konstruktor, der für Unterklassen wichtig ist. Abgeleitete Klassen bieten in der Regel selbst einen Konstruktor mit dem Parameter vom Typ Writer an und rufen im Rumpf mit super(write) den geschützten Konstruktor der Oberklasse FilterWriter auf. Über die initialisierte geschützte Objektvariable out kommen wir dann an diesen Ur-Writer.
Der Weg zum eigenen Filter
Drei Dinge sind für einen eigenen FilterWriter nötig:
|
Unsere Klasse leitet sich von FilterWriter ab. |
|
Unser Konstruktor erwartet als Parameter ein Writer-Objekt und ruft mit super(out) den Konstruktor der Oberklasse, also FilterWriter, auf. Die Oberklasse speichert den Parameter in der geschützten Objektvariablen out, sodass die Unterklassen darauf zugreifen können. |
|
Wir überlagern die drei write()-Methoden und eventuell noch die close()-Methode. Unsere write()-Methoden führen dann die Filterfunktionen aus und geben die wahren Daten an den Writer weiter. |
Beispiel Die nachfolgende Klasse wandelt Zeichen des Stroms in Kleinbuchstaben um. Sie arbeitet mit Hilfsfunktionen der Character-Klasse.
|
 Hier klicken, um das Bild zu Vergrößern
Listing 12.16
LowerCaseWriterDemo.java
import java.io.*;
class LowerCaseWriter extends FilterWriter
{
public LowerCaseWriter( Writer writer )
{
super( writer );
}
@Override
public void write( int c ) throws IOException
{
out.write( Character.toLowerCase((char)c) );
}
@Override
public void write( char[] cbuf, int off, int len ) throws IOException
{
out.write( String.valueOf(cbuf).toLowerCase(), off, len );
}
@Override
public void write( String s, int off, int len ) throws IOException
{
out.write( s.toLowerCase(), off, len );
}
}
public class LowerCaseWriterDemo
{
public static void main( String[] args )
{
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter( new LowerCaseWriter( sw ) );
pw.println( "Eine Zeile für klein und groß" );
System.out.println( sw.toString() );
}
}
Ein HTML-Writer
Unsere nächste Klasse bringt uns etwas näher an das HTML-Format heran. HTML steht für HyperText Markup Language. Wir wollen eine Klasse HTMLWriter entwerfen, die Filter Writer erweitert und Textausgaben in HTML konvertiert. In HTML werden Tags eingeführt, die vom Browser erkannt und besonders behandelt werden. Findet etwa der Browser im HTML-Text eine Zeile der Form <b>Dick</b>, so stellt er den Inhalt »Dick« in fetter Schrift dar, da das <B>-Tag den Zeichensatz umstellt. Alle Tags werden in spitzen Klammern geschrieben. Daraus ergibt sich, dass HTML einige spezielle Zeichenfolgen (Entities genannt) verwendet. Wenn diese Zeichen auf der HTML-Seite dargestellt werden, muss dies durch spezielle Zeichensequenzen geschehen.
Tabelle 12.4
HTML-Zeichen mit alternativen Zeichensequenzen
Zeichen
|
Zeichensequenz
|
<
|
<
|
>
|
>
|
&
|
&
|
Kommen diese Zeichen im Quelltext vor, so muss unser HTMLWriter diese Zeichen durch die entsprechende Sequenz ersetzen. Andere Zeichen sollen nicht ersetzt werden.
Den Browsern ist die Struktur der Zeilen in einer HTML-Datei egal. Sie formatieren wiederum nach speziellen Tags. Absätze etwa werden mit <p> eingeleitet, einfache Zeilenvorschübe mit <br>. Unser HTMLWriter soll zwei leere Zeilen durch einen Absatz-Tag markieren. Demnach sollte unser Programm Leerzeilen zählen.
Alle sauberen HTML-Dateien haben einen wohl definierten Anfang und ein wohl definiertes Ende. Nachfolgendes kleines HTML-Dokument ist wohlgeformt und zeigt, was unser Programm für Einträge machen muss. Auf den DOCTYPE für korrektes XHTML wurde hier verzichtet.
<HTML>
<HEAD><TITLE>
Title of page
</TITLE></HEAD>
<BODY>
</BODY>
</HTML>
Der Titel der Seite sollte im Konstruktor übergeben werden können. Hier nun das Programm für den HTMLWriter:
Listing 12.17
HTMLWriter.java
import java.io.*;
class HTMLWriter extends FilterWriter
{
private boolean newLine;
/**
* Constructor with title of the page.
*/
public HTMLWriter( Writer writer, String title )
{
super( writer );
try {
out.write( "<HTML><HEAD><TITLE>"
+ title
+ "</TITLE></HEAD><BODY>\n" );
} catch ( IOException e ) { e.printStackTrace(); }
}
/**
* Needed constructor without title on the page.
*/
public HTMLWriter( Writer writer )
{
this( writer, "" );
}
/**
* Writes a single character.
*/
@Override
public void write( int c ) throws IOException
{
switch ( c )
{
case '<' : out.write( "<" ); newLine = false; break;
case '>' : out.write( ">" ); newLine = false; break;
case '&' : out.write( "&" ); newLine = false; break;
case '\n': if ( newLine ) {
out.write( "<P>\n" ); newLine = false;
}
else
out.write( "\n" );
newLine = true;
break;
case '\r': break; // ignore
default : out.write( c ); newLine=false;
}
}
/**
* Writes a portion of an array of characters.
*/
@Override public void
write( char[] cbuf, int off, int len ) throws IOException
{
for ( int i = off; i < len; i++ )
write( cbuf[i] );
}
/**
* Writes a portion of a string.
*/
@Override
public void write( String s, int off, int len ) throws IOException
{
for ( int i = off; i<len; i++ )
write( s.charAt( i ) );
}
/**
* Closes the stream.
*/
@Override
public void close() throws IOException
{
try {
out.write( "</BODY></HTML>\n" );
} catch ( IOException e ) { e.printStackTrace(); }
out.close();
}
}
public class HTMLWriterDemo
{
public static void main( String[] args )
{
StringWriter sw = new StringWriter();
HTMLWriter html = new HTMLWriter( sw, "Toll" );
PrintWriter pw = new PrintWriter( html );
pw.println( "Und eine Menge von Sonderzeichen: < und > und &" );
pw.println( "Zweite Zeile" );
pw.println( );
pw.println( "Leerzeile" );
pw.println( "Keine Leerzeile danach" );
pw.close();
System.out.println( sw.toString() );
}
}
Im Demo-Programm erzeugen wir einen StringWriter, in dem wir die Daten ablegen. Wir müssen close() vor der Anweisung sw.toString() aufrufen, da wir andernfalls nicht den korrekten Abschluss sehen würden. Wenn wir nicht über den PrintWriter schließen, sondern über den HTMLWriter, müssten wir noch einen try/catch-Block um close() setzen, da sie eine IOException erzeugt. Nutzen wir aber PrintWriter, kümmert sich dieser darum, die Exception zu fangen.
Die Ausgabe, die unser Programm nun erzeugt, ist folgende:
<HTML><HEAD><TITLE>Toll</TITLE></HEAD><BODY>
Und eine Menge von Sonderzeichen: < und > und &
Zweite Zeile
<P>
Leerzeile
Keine Leerzeile danach
</BODY></HTML>
|