03.07 Reguläre Ausdrücke

Reguläre Ausdrücke (RegExp, Regex, regular expressions) stellen ein Muster in Form einer Zeichenkette dar, anhand dessen eine weitere Zeichenkette aufgeteilt, durchsucht, manipuliert oder überprüft werden kann. Dieses Kapitel bietet Ihnen einen Einstieg in reguläre Ausdrücke.

Sie haben bereits mit regulären Ausdrücken gearbeitet – im Kapitel 03.03. Nützliche String-Methoden. Dort wurden den Methoden split und replaceAll reguläre Ausdrücke übergeben um einen String dementsprechend zu manipulieren. Wie schon oben erwähnt, wird eine regular expression in Form eines Strings behandelt.

Eine regular Expression kann eine ganz normale Zeichenkette sein – z. B. „12345“ um nach der Zahl „12345“ zu suchen. Aber Sie können auch variable Ausdrücke verwenden – z. B. „j-p“ um alle klein geschriebenen Buchstaben zwischen j und p zu finden. Nachfolgend finden Sie einige Anwendungsbeispiele.

Gewöhnliche Zeichen

Wie Sie bereits wissen teilt die String#split-Methode eine gegebene Zeichenkette anhand einer RegExp in ein String-Array auf. Dabei lassen sich ganz gewöhnliche Zeichen wie z. B. das Leerzeichen, ein „A“, das Semikolon oder „xyz“ verwenden. Bei jedem Vorkommen dieser Zeichenkette in unserem zu bearbeitenden String, wird selbiger aufgeteilt und die gefundene Stelle gelöscht.

String blub = "Ich werde Wort für Wort aufgeteilt";
String[] words = blub.split(" ");
for (int i = 0; i < words.length; i++) {
  System.out.println(words[i]);
}

Bitte beachten Sie, dass manche Zeichen maskiert werden müssen (Siehe Escape Sequenzen). Dazu gehört z. B. der Punkt (.). Wird dieser nicht maskiert, zählt er für jedes beliebige Zeichen. Zum Maskieren wird – wie in Java selbst auch – der Backslash verwendet. Hierdurch muss der Backslash doppelt maskiert werden, was teilweise zu recht unübersichtlichen Aneinanderreihungen von Zeichen führt.

String str = "1\\2\\3\\4\\5";
String[] array = str.split("\\\\");
for (int i = 0; i < array.length; i++) {
  System.out.println(array[i]);
}
str = "1.2.3.4.5";
array = str.split("\\.");
for (int i = 0; i < array.length; i++) {
  System.out.println(array[i]);
}

Zeichen-Klassen

Ein String kann nicht nur an gewöhnlichen Zeichen(ketten) getrennt werden, sondern auch anhand abstrakter Definitionen. Wenn Sie eine Zeichenkette z. B. an jeder Zahl zwischen 2 und 6 trennen möchten, so können Sie folgenden Ausdruck verwenden:

String blub = "1 eins 2 zwei 3 drei 4 vier 5 fünf 6 sechs 7 sieben 8 acht 9 neun";
String[] test = blub.split("[2-6]");
for (int i = 0; i < test.length; i++) {
  System.out.println(test[i]);
}

Dieser Block (eckige Klammer) wird als Zeichen-Klasse (engl. character-class) bezeichnet – die Zusammenfassung von mehreren Ausdrücken und/oder Zeichen.

Mit „^“ kann der Ausdruck negiert werden. So könnten Sie mit der Methode String#replaceAll (welche bekanntlich auch einen regulären Ausdruck erwartet) beispielsweise aus einem String alle Zeichen entfernen, die keine Zahlen repräsentieren.

String number = "123a43Bc a sd l43";
String realNumber = number.replaceAll("[^0-9]", "");
System.out.println(realNumber);

Ein paar weitere Ausdrücke:

  • [abc] a, b oder c
  • [^abc] Alles außer a, b oder c
  • [a-zA-Z] Das Alphabet in Groß- und Kleinschreibung
  • [0-9] Alle Zahlen

Vordefinierte Zeichen-Klassen

Des weiteren gibt es auch einige vordefinierte Zeichen-Klassen. Diese beginnen mit einem Backslash gefolgt von einem entsprechenden Zeichen.

  • \d Eine beliebige Zahl
  • \D Keine Zahl
  • \s Ein beliebiges Whitespace-Zeichen (Leerzeichen, Zeilenumbruch, Tabulator, …)
  • \S Kein Whitespace-Zeichen
  • \w Ein Wort-Zeichen ([a-zA-Z_0-9])
  • \W Kein Wort-Zeichen

Selbstverständlich müssen Sie den zugehörigen Backslash in Ihrem Java-Programm ein weiteres Mal maskieren.

Sie finden eine umfangreiche Erklärung/Auflistung von regulären Ausdrücken in der Klassenbeschreibung der Pattern-Klasse in der Java-API Dokumentation.

Überprüfen einer Zeichenkette

Sie können eine Zeichenkette dahingehend überprüfen, ob Sie mit einem regulären Ausdruck übereinstimmt. Hierzu verwenden Sie die Methode String#matches.

String onlyNumbers = "123a3432";
String onlyRegex = "[0-9]*";
if (onlyNumbers.matches(onlyRegex)) {
  System.out.println("Nur zahlen");
}
String noNumbers = "Ich bestehe aus keinen Zahlen!";
String noRegex = "[^0-9]*";
if (noNumbers.matches(noRegex)) {
  System.out.println("Keine Zahlen");
}

Ein Stern hinter einer Zeichen-Klasse bedeutet, dass diese Zeichen-Klasse beliebig oft hintereinander vorkommen darf (also auch kein Mal). Nicht zu verwechseln mit dem Plus (+), welches festlegt, dass die Klasse beliebig oft vorkommen darf, aber mindestens einmal vorkommen muss.

Selbstverständlich gibt es auch komplexere Beispiele, als einen String auf das Vorkommen von Zahlen zu überprüfen. Mit regulären Ausdrücken können z. B. auch IP-Adressen oder E-Mail Anschriften auf Korrektheit überprüft werden.

String number_between_0_and_255 = "(([0-9]{1,2})|([01][0-9]{2})|(2((5[0-5])|([0-4][0-9]))))"; 
String valid_ipv4 = "(" + number_between_0_and_255 + "\\.){3}" + number_between_0_and_255;
String valid_mail = "^[\\w\\.=-]+@[\\w\\.-]+\\.[\\w]{2,4}$";
System.out.println("192.168.178.0".matches(valid_ipv4));
System.out.println("192.168.178.".matches(valid_ipv4));
System.out.println("9.9.9.9".matches(valid_ipv4));
System.out.println("192.168.256.1".matches(valid_ipv4));
System.out.println("127.1.0.0".matches(valid_ipv4));
System.out.println("webmaster@java-blog-buch.de".matches(valid_mail));
System.out.println("a@b.c".matches(valid_mail));
System.out.println("asdf.de".matches(valid_mail));
System.out.println("ich@du.info".matches(valid_mail));
System.out.println("devil666@hell.com".matches(valid_mail));

Eine geschweifte Klammer hinter einem Ausdruck gibt an, wie oft der vorhergehende Ausdruck hintereinander vorkommen muss. Finden sich zwei Zahlen in diesen Klammern – separiert durch ein Komma – repräsentieren diese einen entsprechenden von-bis-Richtwert (eine Zahl gefolgt von einem Komma bedeutet „mindestens x-Mal“). Auch können logische Operatoren wie | oder & verwendet werden um Ausdrücke zu verknüpfen.

Natürlich gibt es bei solch einfachen regulären Ausdrücken immer wieder eigentlich gültige Zeichenketten, die als ungültig markiert werden, und ungültige Ausdrücke die als gültig markiert werden. Um alle Möglichkeiten abzudecken wird ein dementsprechend komplexer Regex benötigt. Oftmals ist es aber nicht nötig wirklich alle fehlerhaften Eingaben auszuschließen. Gültige Eingaben sollten aber nach Möglichkeit alle akzeptiert werden. Beachten Sie auch, dass ein komplexerer Regex viel Rechen-Ressourcen beansprucht.

Eine Vielzahl guter und sehr komplexer regulärer Ausdrücke finden sich auf regexlib.com.

Reguläre Ausdrücke vorkompilieren

Anstelle der Methode String#matches können Sie auch die Klassen Pattern und Matcher verwenden. Diese bieten Ihnen zudem weitere Möglichkeiten um mit regulären Ausdrücken zu arbeiten.

Eine Zeichenkette durchsuchen

Mit Java haben Sie nicht nur die Möglichkeiten zu ersetzen, zu überprüfen und zu teilen, sondern Sie können auch in einer Zeichenkette suchen. Hierzu benötigen Sie die eben erwähnten Klassen Pattern und Matcher (alle im Package java.util.regex).

Nehmen wir an, Sie wollen aus einem Fließtext alle IP-Adressen und den dazugehörige Bezeichnung extrahieren. Zusätzlich wissen Sie, dass die Bezeichnung immer unmittelbar vor der IP-Adresse steht. Einen Regex für IP-Adressen kennen Sie bereits. Dieser wird nun dahingehend erweitert, dass ein zusätzliches Wort vor der IP-Adresse für einen Treffer benötigt wird:

String number_between_0_and_255 = "(([0-9]{1,2})|([01][0-9]{2})|(2((5[0-5])|([0-4][0-9]))))"; 
String valid_ipv4 = "(" + number_between_0_and_255 + "\\.){3}" + number_between_0_and_255;
String regex = "[\\S]*[\\s](" + valid_ipv4 + ")";

Es werden also beliebig viele Zeichen, die keinen Whitespace (\S) repräsentieren gefolgt von einem Whitespace Zeichen (\s) und einer gültigen IP-Adresse gesucht.

Der Text lautet wie folgt:

String text = "Der Client 192.168.178.20 hat die Subnetzmaske " +
  "255.255.255.0 und verbindet sich über sein Standardgateway " +
  "192.168.178.1 zur Außenwelt.";

Mit der Klasse Pattern sollten Sie nun Ihren regulären Ausdruck kompilieren und ein neues Objekt erzeugen.

Häufig vorkommende reguläre Ausdrücke sollten über die Pattern-Klasse kompiliert und somit ein neues Objekt dieser Klasse erzeugt werden. Wenn Sie später mit diesem Ausdruck arbeiten möchten, sparen Sie sich durch diese Maßnahme viel Rechenzeit des Computers.

Pattern pattern = Pattern.compile(regex);

Die Klasse Matcher ist für die Ergebnisse zuständig. Um ein neues Objekt von ihr zu erzeugen, verwenden wir die matcher-Methode unseres Pattern-Objekts.

Matcher matcher = pattern.matcher(text);

Die Methode Matcher#find liefert true zurück, solange im übergebenen Text noch Übereinstimmungen mit dem gewünschten regulären Ausdruck bestehen. Über Matcher#group können Sie den aktuellen Treffer ausgeben. Es ergibt sich also folgende While-Schleife:

while (matcher.find()) {
  System.out.println(matcher.group());
}

Hier nochmal der vollständige Code zum Kompilieren und Ausführen:

package de.test;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class RegExTest {
o
  public static void main(String[] args) {

    String number_between_0_and_255 = "(([0-9]{1,2})|([01][0-9]{2})|(2((5[0-5])|([0-4][0-9]))))"; 
    String valid_ipv4 = "(" + number_between_0_and_255 + "\\.){3}" + number_between_0_and_255;
    String regex = "[\\S]*[\\s](" + valid_ipv4 + ")";
    String text = "Der Client 192.168.178.20 hat die Subnetzmaske " +
      "255.255.255.0 und verbindet sich über sein Standardgateway " +
      "192.168.178.1 zur Außenwelt.";
    Pattern pattern = Pattern.compile(regex);
    Matcher matcher = pattern.matcher(text);
    while (matcher.find()) {
      System.out.println(matcher.group());
    }
  }
}

Ähnliche Beiträge