5.10 Zerlegen von Zeichenketten
Die Java-Bibliothek bietet einige Klassen und Methoden, mit denen wir nach bestimmten Mustern große Zeichenketten in kleinere zerlegen können. In diesem Kontext sind die Begriffe Token und Delimiter zu nennen: Ein Token ist ein Teil eines Strings, den bestimmte Trennzeichen (engl. delimiter) von anderen Tokens trennen. Nehmen wir als Beispiel den Satz »Moderne Musik ist Instrumentespielen nach Noten« (Peter Sellers). Wählen wir Leerzeichen als Trennzeichen, lauten die einzelnen Tokens »Moderne«, »Musik« usw.
Die Java-Bibliothek bietet eine Reihe von Möglichkeiten zum Zerlegen von Zeichenfolgen. Die ersten beiden aus dieser Liste werden in den nachfolgenden Abschnitten vorgestellt:
split(…) von String: Aufteilen mit einem Delimiter, den ein regulärer Ausdruck beschreibt
lines() von String: Liefert einen Stream<String> von Zeilen.
Scanner: schöne Klasse zum Ablaufen einer Eingabe, auch zeilenweise
StringTokenizer: der Klassiker aus Java 1.0. Delimiter sind nur einzelne Zeichen.
BreakIterator: Findet Zeichen-, Wort-, Zeilen- oder Satzgrenzen.
Matcher: In Zusammenhang mit der Pattern-Klasse zerlegt Matcher Zeichenfolgen mithilfe von regulären Ausdrücken.
Die Methoden und Klassen sind sozusagen die Gegenspieler der Konkatenationsmöglichkeiten: split(…) steht join(…) gegenüber, StringTokenizer dem StringJoiner.
5.10.1 Splitten von Zeichenketten mit split(…)
Die Objektmethode split(…) eines String-Objekts zerlegt die eigene Zeichenkette in Teilzeichenketten. Die Trenner sind völlig frei wählbar und als regulärer Ausdruck beschrieben. Die Rückgabe ist ein Array bzw. ein Iterable mit Teilzeichenketten.
[zB] Beispiel
Zerlege einen Domain-Namen in seine Bestandteile:
String path = "www.tutego.com";
String[] segs = path.split( Pattern.quote( "." ) );
System.out.println( Arrays.toString(segs) ); // [www, tutego, com]
Da der Punkt als Trennzeichen ein Sonderzeichen für reguläre Ausdrücke ist, muss er passend mit dem Backslash auskommentiert werden. Das erledigt die statische Methode Pattern.quote(String), die einen »entschärften« Regex-String zurückgibt. Andernfalls liefert split(".") auf jedem String ein Array der Länge 0.
Ein häufiger Trenner ist \s, also Weißraum.
[zB] Beispiel
Zähle die Anzahl der Wörter in einem Satz:
String string = "Hört es euch an, denn das ist mein Gedudel!";
int nrOfWords = string.split( "(\\s|\\p{Punct})+" ).length;
System.out.println( nrOfWords ); // 9
Der Trenner ist entweder Weißraum oder ein Satzzeichen. Alternativ kann auch der Ausdruck "[\\s\\p{Punct}]+" eingesetzt werden.
final class java.lang.String
implements CharSequence, Comparable<String>, Serializable
String[] split(String regex)
Zerlegt die aktuelle Zeichenkette mit dem regulären Ausdruck.String[] split(String regex, int limit)
Zerlegt die aktuelle Zeichenkette mit dem regulären Ausdruck, liefert jedoch maximal begrenzt viele Teilzeichenfolgen.
5.10.2 Yes we can, yes we scan – die Klasse Scanner
Die Klasse java.util.Scanner kann eine Zeichenkette in Tokens zerlegen und einfache Dateien zeilenweise einlesen. Bei der Zerlegung kann ein regulärer Ausdruck den Delimiter beschreiben. Damit ist Scanner flexibler als ein StringTokenizer, der nur einzelne Zeichen als Trenner zulässt.
Aufbauen eines Scanners
Zum Aufbau der Scanner-Objekte bietet die Klasse einige Konstruktoren an, die die zu zerlegenden Zeichenfolgen unterschiedlichen Quellen entnehmen, etwa einem String, einem Datenstrom (beim Einlesen von der Kommandozeile ist das System.in), einem Path-Objekt oder diversen anderen Eingabequellen. Falls ein Objekt vom Typ Closeable dahintersteckt, wie ein Writer, sollte mit close() der Scanner geschlossen werden, der das close() zum Closeable weiterleitet. Beim String ist das nicht nötig.
final class java.util.Scanner
implements Iterator<String>, Closeable
Scanner(String source)
Scanner(Path source) throws IOException
Scanner(Path source, String charsetName) throws IOException
Scanner(Path source, Charset charset) throws IOException (seit Java 10)
Scanner(File source) throws FileNotFoundException
Scanner(File source, String charsetName) throws FileNotFoundException
Scanner(File source, Charset charset) throws IOException (seit Java 10)
Scanner(InputStream source)
Scanner(InputStream source, String charsetName)
Scanner(Readable source)
Scanner(ReadableByteChannel source)
Scanner(ReadableByteChannel source, String charsetName)
Scanner(ReadableByteChannel source, Charset charset) (seit Java 10)
Scanner(InputStream source, Charset charset) (seit Java 10)
Zeilenweises Einlesen einer Datei
Ist das Scanner-Objekt angelegt, lässt sich mit dem Paar hasNextLine() und nextLine() einfach eine Datei zeilenweise auslesen:
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.util.Scanner;
public class PrintAllLines {
public static void main( String[] args ) throws IOException {
try ( Scanner scanner = new Scanner( Paths.get( "EastOfJava.txt" ),
StandardCharsets.ISO_8859_1.name() ) ) {
while ( scanner.hasNextLine() )
System.out.println( scanner.nextLine() );
}
}
}
Da der Konstruktor von Scanner mit der Datei eine Ausnahme auslösen kann, müssen wir diesen möglichen Fehler behandeln. Wir machen es uns einfach und leiten einen möglichen Fehler an die Laufzeitumgebung weiter. Die Konstruktion mit try (…) { … } nennt sich try mit Ressourcen und schließt automatisch die Datei nach der Nutzung. Der Umgang mit Exceptions und das besondere try werden beide in Kapitel 8, »Ausnahmen müssen sein«, genauer erklärt. Auch sollte immer die Kodierung angegeben werden, die in unserem Fall ISO 8859-1, also Latin-1, ist. Der Kodierungs-String, der für den Scanner-Konstruktor nötig ist, stammt von einer Konstanten aus StandardCharsets.
final class java.util.Scanner
implements Iterator<String>, Closeable
boolean hasNextLine()
Liefert true, wenn eine nächste Zeile gelesen werden kann.String nextLine()
Liefert die nächste Zeile.
[+] Tipp
Wenden wir Scanner auf einen String an, so kann dieser String vom Scanner Zeile für Zeile zerlegt werden. Seit Java 11 bietet die String-Methode lines() eine Alternative.
Der Nächste, bitte
Nach dem Erzeugen des Scanner-Objekts liefert die Methode next() die nächste Zeichenfolge, wenn denn ein hasNext() die Rückgabe true ergibt. (Das sind dann auch die Methoden der Schnittstelle Iterator, wobei remove() nicht implementiert ist.)
[zB] Beispiel
Von der Standardeingabe soll ein String gelesen werden:
Scanner scanner = new Scanner( System.in );
String s = scanner.next();
Wichtig: Der Scanner sollte in diesem Fall nicht geschlossen werden!
Neben der next()-Methode, die nur einen String als Rückgabe liefert, bietet Scanner diverse next<Typ>()-Methoden an, die das nächste Token einlesen und in ein gewünschtes Format konvertieren, etwa in ein double bei nextDouble(). Über gleich viele hasNext<Typ>()-Methoden lässt sich erfragen, ob ein weiteres Token dieses Typs folgt.
[zB] Beispiel
Wir betrachten die einzelnen nextXXX()- und hasNextXXX()-Methoden an einem Beispiel:
Scanner scanner = new Scanner( "tutego 12 1973 12,03 True 123456789000" );
System.out.println( scanner.hasNext() ); // true
System.out.println( scanner.next() ); // tutego
System.out.println( scanner.hasNextByte() ); // true
System.out.println( scanner.nextByte() ); // 12
System.out.println( scanner.hasNextInt() ); // true
System.out.println( scanner.nextInt() ); // 1973
System.out.println( scanner.hasNextDouble() ); // true
System.out.println( scanner.nextDouble() ); // 12.03
System.out.println( scanner.hasNextBoolean() ); // true
System.out.println( scanner.nextBoolean() ); // true
System.out.println( scanner.hasNextLong() ); // true
System.out.println( scanner.nextLong() ); // 123456789000
System.out.println( scanner.hasNext() ); // false
Sind nicht alle Tokens interessant, überspringt Scanner skip(Pattern pattern) bzw. Scanner skip(String pattern) sie – Trennzeichen werden nicht beachtet.
final class java.util.Scanner
implements Iterator<String>, Closeable
boolean hasNextBigDecimal()
boolean hasNextBigInteger()
boolean hasNextBigInteger(int radix)
boolean hasNextBoolean()
boolean hasNextByte()
boolean hasNextByte(int radix)
boolean hasNextDouble()
boolean hasNextFloat()
boolean hasNextInt()
boolean hasNextInt(int radix)
boolean hasNextLong()
boolean hasNextLong(int radix)
boolean hasNextShort()
boolean hasNextShort(int radix)
Liefert true, wenn ein Token des gewünschten Typs gelesen werden kann.String next()
BigDecimal nextBigDecimal()
BigInteger nextBigInteger()
BigInteger nextBigInteger(int radix)
boolean nextBoolean()
byte nextByte()
byte nextByte(int radix)
double nextDouble()
float nextFloat()
int nextInt()
int nextInt(int radix)
long nextLong()
long nextLong(int radix)
short nextShort()
short nextShort(int radix)
Liefert das nächste Token.
Die Methode useRadix(int) ändert die Basis für Zahlen, und radix() erfragt sie.