520 likes | 702 Views
Grundlagen der Informatik 1 Thema 20: Ströme und Ein-/Ausgabe in Java. Prof. Dr. Max Mühlhäuser Dr. Guido Rößling. Inhaltsverzeichnis. Einführung in Eingabe-/Ausgabe-Ströme und Java Ein-/Ausgabe (Input/Output, I/O) Einblick in Prozessströme
E N D
Grundlagen der Informatik 1Thema 20: Ströme und Ein-/Ausgabe in Java Prof. Dr. Max Mühlhäuser Dr. Guido Rößling
Inhaltsverzeichnis • Einführung in Eingabe-/Ausgabe-Ströme und Java Ein-/Ausgabe (Input/Output, I/O) • Einblick in Prozessströme • Schachtelung von Stromobjekten und das Decorator-Muster • Selbstdefinierte Ströme, StreamTokenizer und wahlfreier Zugriff
Eingabe /Ausgabe (Input / Output, I/O) • Bislang: • Eingabedaten sind entweder fest im Quelltext codiert oder werden als Parameter beim Programmstart von der Konsole übergeben • define in Scheme • Variablendeklaration und Initialisierung in Java • Tatsächliche Parameterwerte in Funktionsaufrufen von Scheme • args Parameter in main • Ausgaben des Programms wurden auf dem Bildschirm ausgegeben • Probleme: • „Closed World“ • keine Interaktion mit der Außenwelt • Keine Einflussnahme auf den Programmablauf nach dem Start • Ergebnisse können nicht dauerhaft gespeichert werden • Diese Vorlesung widmet sich anderer Möglichkeiten der Ein-/Ausgabe in Java
I/O-Ströme • Verschiedene Quellen/Senken für die Daten • Tastatureingabe („Standard I/O“) • Datei(en) auf dem lokalen Rechner („File I/O“) • Datei(en)/Prozess(e) im Netzwerk („Network I/O“) • Hauptspeicher („Memory I/O“) • Um einheitlich Daten auf unterschiedliche „Datenbehälter“ ein-/auszugeben, verwendet Java das Konzept der „Streams“ (Ströme) • Eingabe- und Ausgabeströme (E/A, engl. I/O) • Datenströme als eine Abstraktion von I/O “Geräten“ • Verstecken Details über die Implementierung / Funktionsweise der einzelnen I/O-Geräte vor dem Java Programmierer • Tastatur, Datei, anderes Programm, Netz, Hauptspeicher, … • Stellen Java-Programmen einheitliche Schnittstellen zum Lesen bzw. Schreiben von Daten zur Verfügung • Das Java Programm “spricht“ mit Java I/O Objekten
I/O-Ströme • Um Daten von einer externen Datenquelle zu lesen, öffnet ein Java-Programm einen Eingabestrom und liest die Daten seriell (nacheinander) ein: • Um Daten in eine externe Senke zu schreiben, öffnet ein Java-Programm einen Ausgabestrom und schreibt die Daten seriell
Ströme und verzögerte Listen • Das Konzept der Ströme ist allgemeiner und nicht nur für I/O geeignet • Die Idee haben wir bereits in Scheme kennen gelernt • Verzögerte Listen • Möglichkeit, „virtuelle“ Listen zu erzeugen • Wir konnten transparent Daten mit first und restverknüpfen • Diese Idee passt sehr gut zu I/O • Input: Lese den Eingabewert beim Zugriff auf das nächste Listenelement • Output: Beim Erweitern der Liste Ausgabewert schreiben • Eingabestream verzögerte Liste ohne cons • Ausgabestream verzögerte Liste ohne first/rest
Vordefinierte Ströme in java.io • Zwei Klassifizierungen von Datenströmen: • Nach dem Datentyp • Nach der Struktur der Ströme
Klassifizierung der Ströme • Nach dem Datentyp • Zeichenströme lesen / schreiben char (16-bit Unicode Zeichensatz) • Byteströme lesen / schreiben byte (8-bit) • Werden zum Lesen bzw. Schreiben von Binärdaten, z.B. Bildern, benutzt • Nach der Struktur der Ströme: • Datensenkeströme: Daten werden direkt von „physikalischer“ Datenquelle gelesen bzw. auf „physikalische“ Datensenke geschrieben • Prozessströme: Daten werden von anderen Strömen gelesen bzw. auf andere Ströme geschrieben • Daten werden nach dem Lesen bzw. vor dem Schreiben gefiltert, gepuffert, bearbeitet, usw.
Hierarchie der Datenströme in Java • Zeichenströme • java.io.Reader/java.io.Writer stellen die Schnittstelle und eine partielle Implementierung von Zeichenströmen zur Verfügung • Subklassen von Reader/Writer fügen neues Verhalten hinzu bzw. ändern dieses. • Byteströme • java.io.InputStream/java.io.OutputStream:gemeinsame Schnittstelle und partielle Implementierung für alle Ströme zum Lesen bzw. Schreiben von Bytes • Alle anderen Byteströme sind Unterklassen davon
Reader StringReader Writer InputStreamReader StringWriter FileReader FilterReader PushbackReader OutputStreamWriter FileWriter BufferedReader LineNumberReader FilterWriter BufferedWriter PrintWriter Hierarchie der Zeichenströme (Auszug) Die Klassen in schattierten Kästchen sind Prozessströme
FileInputStream StringBufferInputStream PipedInputStream InputStream ByteArrayInputStream BufferedInputStream ObjectInputStream LineNumberInputStream FilterInputStream PushbackInputStream DataInputStream SequenceInputStream FileOutputStream PipedOutputStream OutputStream ByteArrayOutputStream BufferedOutputStream FilterOutputStream PrintStream ObjectOutputStream DataOutputStream Hierarchie der Byteströme (Auszug) Die Klassen in schattierten Kästchen sind Prozessströme
Datensenke-Ströme: Überblick Senke Typ Zeichenströme Byteströme CharArrayReader, CharArrayWriter ByteArrayInputStream, ByteArrayOutputStream Hauptspeicher StringReader, StringWriter StringBufferInputStream PipedReader, PipedWriter PipedInputStream, PipedOutputStream Pipe FileReader, FileWriter FileInputStream, FileOutputStream Datei • CharArrayReader/CharArrayWriter • ByteArrayInputStream/ByteArrayOutputStream • Lesen / Schreiben von / auf Arrays • StringReader/StringWriter, StringBufferInputStream • Lesen / Schreiben von / auf Strings im Hauptspeicher • FileReader/FileWriter, FileInputStream/FileOutputStream • Lesen / Schreiben von / auf Dateien
Gemeinsame Muster der I/O-Ströme • Lesen/Schreiben von/auf Strömen haben unabhängig von Datentyp und Quelle bzw. Senke die gleiche Form • Lesen • Öffne einen Strom • Ströme werden beim Erzeugen automatisch geöffnet • Lese Daten, solange nötig und es noch Daten gibt • Schließe den Strom • Bei Beenden des Lesens bzw. Schreibens sollte der Strom durch close()geschlossen werden! • Schreiben • Öffne einen Strom • Solange es noch Daten gibt, schreibe Daten • Schließe den Strom
I/O-Oberklassen • Nutze I/O Ströme nur über allgemeines Interface • Information Hiding durch Subtyppolymorphie • InputStream • publicint read() • publicint read(byte[] bbuf) • publicint read(byte[] bbuf, int offset, int len) • Reader • publicint read() • publicint read(char[] cbuf) • publicint read(char[] cbuf, int offset, int len) OutputStream publicint write(int b) publicint write(byte[] bbuf) publicint write(byte[] bbuf, int offset, int len) Writer publicint write(int c) publicint write(char[] cbuf) publicint write(char[] cbuf, int offset, int len)
Die Klasse java.io.Reader publicint read(char[] c) throws IOException publiclong skip(long n) throws IOException publicvoid reset() throws IOException publicint close() throws IOException ... • publicabstractintread(char[] c)throws IOException • Liest die nächsten Unicode-Zeichen im Strom in ein Array • Gibt die Anzahl der gelesenen Zeichen zurück oder -1, falls das Ende der Datei erreicht wurde („EOF“) • IOException bei allen anderen Problemen • Z.B. Strom bereits geschlossen, Netzwerkverbindung verloren Für weitere Details siehe API Spezifikation http://java.sun.com/javase/6/docs/api/
Dateiströme • Dateiströme sind Ein-/Ausgabe-Ströme, deren Quellen/Senken Dateien im Dateisystem sind • FileReader/ FileWriterfür Lesen / Schreiben von Zeichen aus/in Dateien • Analog dazu: FileInputStream, FileOutputStream für Lesen / Schreiben von Bytes von/in Dateien • Ein Dateistrom kann erzeugt werden, indem man die Quelle- bzw. Senke-Datei durch eines der folgenden Objekte als Parameter des Strom-Konstruktors übergibt • Dateiname (String) • Datei-Objekt (java.io.File) • Dateibeschreibung (java.io.FileDescriptor)
Die Klasse java.io.FileReader • Beispiel: Ausgeben eines Dateiinhalts auf den Bildschirm (Ausgabe erfolgt als Integer-Werte!) und Zeichen von 'a' bis 'z' in eine Datei schreiben importjava.io.FileReader; importjava.io.FileWriter; importjava.io.IOException; publicclassReadWriteFile { // Konstruktor etc. publicvoidreadFrom(String fileName) throwsIOException { FileReader in = newFileReader(fileName); int b; while ((b = in.read()) != -1) System.out.print(b); in.close(); } publicvoid writeAToZ(String filename) throws IOException { FileWriter out = new FileWriter(filename); for (charc = 'a'; c <= 'z'; c++) out.write(c); out.close(); } // main etc. }
Inhaltsverzeichnis • Einführung in Ein-/Ausgabe-Ströme und Java Ein-/Ausgabe (Input/Output, I/O) • Einblick in Prozessströme • Schachtelung von Stromobjekten und das Decorator-Muster • Selbstdefinierte Ströme, StreamTokenizer und wahlfreier Zugriff
Originaler Datenstrom Prozess-Datenstrom Datensenke Datenquelle Prozess-Ströme • Ein Prozess-Strom enthält einen anderen (Daten- oder Prozess-)Strom • Dieser dient als Quelle bzw. Senke • Prozess-Ströme ändern Daten oder bieten Funktionalität • Zwischenspeichern (Puffern) von Daten • Zählen der gelesenen/geschriebenen Zeilen • Konvertierung zwischen Byte und Zeichen • Kompression, …
Prozess-Ströme Aufgabe Zeichenströme Byteströme FilterReader, FilterWriter FilterInputStream, FilterOutputStream Filtern BufferedReader, BufferedWriter BufferedInputStream, BufferedInputStream Puffern byte char InputStreamReader, OutputStreamWriter Byte / Zeichen Konvertierung char byte LineNumberReader LineNumberInputStream Zählen Unterstützung für Datentypen DataInputStream, DataOutputStream Rückgängig machen PushbackReader PushbackInputStream PrintStream PrintWriter Ausgeben
Die Wurzel der Prozess-Ströme • FilterReader/FilterWriterbzw. FilterInputStream/FilterOutputStreamsind Superklassen für alle Prozessströme • Kapseln einen internen Strom in • Defaultimplementierung einer Operation op: delegiere op weiter an in(in.op()) • Konkrete Unterklassen redefinieren diese Funktionalität
Pufferströme • Ein Pufferstrom kapselt einen anderen Datenstrom und einen internen Puffer • Beim ersten Lesen wird der Puffer vollständig gefüllt • Weitere Lese-Operationen liefern Bytes vom Puffer zurück, ohne vom unterliegenden Strom tatsächlich zu lesen • Bei leerem Puffer wird erneut vom unterliegenden Strom gelesen • Beim Schreiben werden die Daten zuerst in dem internen Puffer gespeichert, bevor sie in den unterliegenden Strom geschrieben werden • Nur wenn der Puffer voll ist, wird auf den unterliegenden Strom geschrieben • Sowie bei explizitem Aufruf der Methode flush() oder close()
Pufferströme • BufferedReaderund BufferedWriterbieten die Standard-Schnittstelle und Implementierung für Zeichenströmemit Pufferung an • Unterklassen von Reader bzw. Writer • BufferedInputStreamund BufferedOutputStream bieten die Standard-Schnittstelle und Implementierung für Byteströmemit Pufferung an • Unterklassen von FilterInputStream bzw. FilterOutputStream
Pufferströme Datensenke BufferedWriter Programm b write bbuf write bbuf write bbuf write flush
Lesen einer 2.5 MB-Datei (x: Puffergröße, y: Zeit [ms] Pufferströme: Performanz
DataInputStream DataOutputStream readBoolean(): boolean readByte(): byte readShort() : short readChar() : char readInt() : int readFloat() : float writeBoolean(boolean) : void writeByte(byte) : void writeShort(short) : void writeChar(char) : void writeInt(int) : void writeFloat(float) : void FilterInputStream FilterOutputStream <<interface>> DataInput <<interface>> DataOutput DataInputStream / DataOutputStream • Diese Ströme ermöglichen das Lesen/Schreiben von primitiven Java Datentypen in einem maschinenunabhängigen Format. • Ein DataOutputStreamwird verwendet, um Daten zu schreiben, die später von einemDataInputStreamgelesen werden sollen.
Datenströme: Beispiel import java.io.FileOutputStream; import java.io.DataOutputStream; importjava.io.IOException; publicclass DataStreamExample { String fileName = "hello.txt";// ein Dateiname // ein paar Methoden publicvoid writeData()throws IOException{ FileOutputStream fos = new FileOutputStream(filename); DataOutputStream out = newDataOutputStream(fos); out.writeInt(9); out.writeDouble(Math.PI); out.writeBoolean(true); out.close(); } // weitere Methoden publicvoid readData()throws IOException { FileInputStream fis = new FileInputStream(fileName); DataInputStream in = new DataInputStream(fis); int i = in.readInt(); double d = in.readDouble(); boolean b = in.readBoolean(); in.close(); System.out.println("Gelesen: "+ i + ", " +d+ ", und " +b+ "."); } }
Standard-I/O • Im Paket java.langgibt es die Klasse System mit den folgenden Klassenvariablen: System.in Standardeingabe (Tastatur) System.out Standardausgabe (Bildschirm) System.err Fehlermeldungen (Bildschirm) • System.inist ein Objekt vom Typ InputStream • Stelltread()zum byte-weisen Lesen von Bytes von der Tastatur zur Verfügung • System.out, System.errsind vom Typ PrintStream • Bieten print() und println() zur Datenausgabe an
PrintStream print(boolean) : void print(double) : void print(char) : void print(double) : void print(float) : void ... println(Object): void println(String): void ... PrintStream • Ermöglicht das einfache Schreiben von diversen Datentypen in einer üblichen Repräsentation • Wirft niemals eineIOException • Auftretende Exceptions setzen ein internes Flag, das durch die checkErrorMethode getestet werden kann • Alle Zeichen, die mit einem PrintStreamgeschrieben werden, werden mit dem Standardzeichensatz der Plattform konvertiert • PrintWritersollte in Situationen eingesetzt werden, in denen Zeichen (anstelle von Bytes) geschrieben werden FilterOutputStream
Inhaltsverzeichnis • Einführung in Ein-/Ausgabe-Ströme und Java Ein-/Ausgabe (Input/Output, I/O) • Einblick in Prozessströme • Schachtelung von Stromobjekten und das Decorator-Muster • Selbstdefinierte Ströme, StreamTokenizer und wahlfreier Zugriff
Ströme können ineinander verschachtelt werden Abstraktionsebenen, bei denen unterliegende „primitive“ Ströme von umschließenden („höheren“, komfortableren) Strömen benutzt werden („Prozessströme“) Verschachtelung der Ströme BufferedOutputStream ZipOutputStream FileOutputStream Datensenke, z.B. Datei // erzeugt gepufferten, komprimierenden Dateiausgabestrom OutputStream out = new FileOutputStream(filename); out = new BufferedOutputStream(out); out = newZipOutputStream(out); // … mehr Eigenschaften koennen dynamisch hinzugefuegt werden // Die Stromeigenschaften sind unsichtbar fuer Klienten
Architektur von Java Streams • Die Technik, mit der erreicht wird, dass Ströme beliebig zur Laufzeit kombiniert werden können, ist nicht nur im Kontext von Strömen von Interesse. • Generelle Technik, um Objekte dynamisch mit Features zu erweiterten • In der Softwaretechnik werden solche Techniken in der Form so genannter Entwurfsmuster (design patterns) dokumentiert. • Wieder verwendbare, dokumentierte Designideen • Mehr in der Hauptstudiumsvorlesung „SE Design“ • Die oben genannte Technik bei Streams ist als „Decorator Pattern“ bekannt.
Inhaltsverzeichnis • Einführung in Ein-/Ausgabe-Ströme und Java Ein-/Ausgabe (Input/Output, I/O) • Einblick in Prozessströme • Schachtelung von Stromobjekten und das Decorator-Muster • Selbstdefinierte Ströme, StreamTokenizer und wahlfreier Zugriff
Selbstdefinierte Prozessströme • Oft möchten Programmierer eigene Ströme definieren, die Daten filtern und/oder verarbeiten, die von einem anderen Strom gelesen/geschrieben werden • Eigene Filterung oder Verarbeitung kann implementiert werden, indem man von FilterReader/FilterInputStream bzw. FilterWriter/FilterOutputStream erbt • Es folgt ein Beispiel eines selbstdefinierten Filterstroms auf Datenströmen, der alle Zeilen im unterliegenden Strom herausfiltert, die einen bestimmten Substring enthalten • Unix "grep" Befehl
Selbstdefinierte Prozessströme importjava.io.BufferedReader; importjava.io.FilterReader; importjava.io.IOException; classGrepReaderextends FilterReader { String substring; BufferedReader in; GrepReader(BufferedReaderreader, String pattern) { super(reader); in = reader; substring = pattern; } // return the next line containing the search pattern String readLine() throwsIOException { String line; while (((line = in.readLine()) != null) && (line.indexOf(substring) == -1)) ; returnline; } }
Selbstdefinierte Prozessströme import java.io.*; publicclassGrep { publicstaticvoid main(String[] args) { if ((args.length == 0) || (args.length > 2)) { System.out.println("Usage: javaGrep <substring> [<filename>]"); System.exit(0); } try { Reader d; if (args.length == 2) d = newFileReader(args[1]); else d = newInputStreamReader(System.in); GrepReader g = newGrepReader(newBufferedReader(d), args[0]); String line; while ((line = g.readLine()) != null) System.out.println(line); g.close(); } catch (IOException e) { System.err.println(e); } } } Zu findendes Muster Durchsuchter Datenstrom
Wahlfreier Zugriff • Bisher: Ströme, deren Inhalt nur in einer sequentiellen Weise gelesen bzw. geschrieben werden kann • Sequentielle Dateien passen gut zu sequentiellen Speichermedien, z.B. Magnetbänder • Random Access Files erlauben nicht-sequentielle (wahlfreie) Zugriffe auf den Inhalt einer Datei
Warum wahlfreier Zugriff? • Gegeben ein Dateiarchiv in .zip Format: • Zip-Archive bestehen aus Dateien und sind meist komprimiert, um Speicher zu sparen • Außer Dateien haben Zip-Archive ganz zum Schluss zusätzlich einen so genannten Dir-Eintrag (von Directory, engl. Verzeichnis) • Hier wird gespeichert, welche Dateien enthalten sind und wo die einzelnen Dateien innerhalb des Archivs anfangen • Aufgabe: eine bestimmte Datei aus dem Archiv extrahieren
Darum wahlfreier Zugriff! • Verfahren für einen sequentiellen Strom: • Suche durch das ganze Archiv, bis die gewünschte Datei lokalisiert ist • Extrahiere die Datei • Im Durchschnitt wird die Hälfte der Einträge im Archiv gelesen, bevor die gewünschte Datei gefunden ist • Strom mit wahlfreiem Zugriff: • Springe zum Dir-Eintrag und lese den Eintrag der gesuchten Datei • Springe rückwärts zu der Position der Datei • Extrahiere die Datei • Man muss genau zwei Einträge lesen: dir-entryandfile • Das Verfahren ist also wesentlich effizienter!
Realisiert durch die Klasse RandomAccessFile Kann für Lesen und Schreiben benutzt werden - implementiert Schnittstellen DataInput und DataOutput Ähnelt FileInputStream und FileOutputStream, indem man ein RandomAccessFile auf eine Datei öffnet und beim Erzeugen den Dateinamen oder ein File-Objekt als Parameter übergibt: Außerdem muss beim Erzeugen spezifiziert werden, ob die Datei nur zum Lesen oder auch zum Schreiben geöffnet wird Man muss eine Datei lesen können, um auf sie schreiben zu können Wahlfreier Zugriff in Java
Wahlfreier Zugriff in Java • Erzeugen eines RandomAccessFile, um die Datei „random.txt“ zu lesen: • new RandomAccessFile("random.txt", "r"); • Erzeugen eines RandomAccessFile, um die Datei „random.txt“ zu lesen und zu schreiben: • new RandomAccessFile("random.txt", "rw"); • Nachdem die Datei geöffnet ist, kann mit den read und write-Operationen gelesen bzw. geschrieben werden
Wahlfreier Zugriff in Java • RandomAccessFile unterstützt einen Datei-Zeiger • indiziert die aktuelle Dateiposition • Beim Erzeugen ist der Datei-Zeiger 0 - der Anfang der Datei • Aufrufe von read / write- Operationen verschieben den Datei-Zeiger automatisch um die Anzahl der gelesenen bzw. geschriebenen Bytes • RandomAccessFileunterstützt zusätzlich Operationen zum Manipulieren des Zeigers: • publicintskipBytes(int n) throwsIOException • Verschiebt den Zeiger vorwärts um nBytes • public native voidseek(longpos) throwsIOException • Positioniert den Zeiger genau vor das Byte an der Stelle pos • public native longgetFilePointer() throwsIOException • Gibt die aktuelle Position in der Datei zurück
Stromverarbeitung mit StreamTokenizer • java.io.StreamTokenizer unterstützt die Zerlegung von Zeichenströmen in Symbole („Tokens“) • Lesen des nächsten Tokens durch nextToken() • Überspringt Whitespace („leere Zeichen“) • Was als Whitespace angesehen wird, ist konfigurierbar • Der Typ des gelesenen Tokens wird über das Attribut ttype abgefragt • Die Tokenarten werden auf der folgenden Folie vorgestellt
StreamTokenizer: Tokenarten • StreamTokenizer.TT_NUMBER: Zahl erkannt, nval enthält die Zahl • StreamTokenizer.TT_WORD: Wort erkannt, sval enthält das Wort • StreamTokenizer.TT_EOL: Zeilenende ist erkannt • Wird nur dann als Token erkannt, wenn der Tokenizer entsprechend konfiguriert wurde (eolIsSignificant(true)) • StreamTokenizer.TT_EOF – Dateiende wurde erreicht • „Sonstiges“ – ttype codiert ein Zeichen, etwa '$', ';'
Anpassung von StreamTokenizer • public void wordChars(int low, int hi) • Definieren, was als Wortbestandteil gelten soll • Alle Zeichen im Intervall [low, hi] zählen als Wortbestandteile • public void whitespaceChars(int low, int hi) • Definieren von Leerzeichen • Alle Zeichen im Intervall [low, hi] zählen als „Leerzeichen“ • public void eolIsSignificant(boolean flag) • Definieren, ob Zeilenende relevant ist • public void quoteChar(int quoteChar) • Definieren von Anführungszeichen • Zahlreiche andere Features – bitte in die Doku sehen!
StreamTokenizer: Anwendungsbeispiel • Eine Teeliste mit der folgenden Struktur soll geparst werden: • Jeder Tee steht in einer eigenen Zeile • Zeilenende ist relevant! • Die Teenamen sind durch Anführungszeichen „gequotet“ • Hinter dem Teenamen stehen der Reihe nach • Dauer des Ziehenlassens [s] • Anzahl Teelöffel pro Liter • Diese Einträge sind jeweils durch Leerzeichen getrennt • Mittels StreamTokenizer ist das Parsen einfach! "Ali Babas 40 Düfte" 180 5 "Asatsuyu" 90 7 "Generic Black Tea" 180 5 "Caramel" 120 6 "Ceylon Pekoe" 120 6 "China Jasmin" 120 6 "Cool Down" 1273 0 "Einer für alle" 540 6 "Erdbeer-Sahne" 120 6 "Erfrischungskräutertee" 480 6 "Früchtepfund" 420 6
StreamTokenizer: Anwendungsbeispiel publicvoidparseTeas(Reader r)throwsIOException { StreamTokenizerstok = newStreamTokenizer(r); stok.eolIsSignificant(true);// EOL melden stok.quoteChar('\"'); // " als Worttrenner inttoken = 0; String teaName = null; intnrSpoons, nrSecs, coolDown; while((token = stok.nextToken()) != StreamTokenizer.TT_EOF) { teaName = stok.sval; // 1. Token token = stok.nextToken(); nrSpoons = (int)stok.nval;// 2. Token token = stok.nextToken(); nrSecs = (int)stok.nval;// 3. Token token = stok.nextToken(); // must be EOL System.out.println(teaName +":" + nrSpoons +" Loeffel, Ziehzeit: " + nrSecs); } } Normalerweise sollte man vor einer Zuweisung den Typ prüfen!