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 { 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()); } } }
Charakter-Klassen?
Bei Marx gibt es den Klassencharakter, aber eine character-class würde ich entweder nicht übersetzen, oder mit Zeichenklasse.
Zeichenklasse ist doch gut.
Zurück zu Marx: Charaktermasken und Warencharakter gibt es da, aber Klassencharakter vielleicht doch nicht. 🙂
Hallo „user unknown“,
Vielen Dank für den Hinweis 🙂 ! Charakter-Klasse war wohl wirklich ein bisschen unglücklich ausgedrückt. Ich habe den Namen in Zeichen-Klasse abgeändert.
Gruß
Stefan
RegExp sind wirklich sehr nützlich. Danke für die gut verständliche Erklärung! LG, der Lehrling
Warum wird hier nicht mit Patterns und Matchern gearbeitet? Ich kenne reguläre Ausdrücke bisher eigentlich nur in der Variante, habe mich jedoch noch nicht soviel damit beschäftigt. Wo liegt der Unterschied? Als Beispiel: im Prinzip könnte ich ja auch hier einfach die Wörter auftrennen (z.B. bei Leerzeichen) und danach mit bestimmten Wörtern vergleichen. Viele Grüße.
Hallo rt,
Es wird doch mit Pattern und Matchern gearbeitet!?
Der Unterschied liegt u. a. in der Einfachheit, wie die Ausdrücke verwendbar sind. Ein einfaches
split
, das sofort ein Ergebnis in Form eines Arrays zurückliefert, ist natürlich viel einfacher zu verwenden, als das Ganze überPattern
undMatcher
zu realisieren. Zudem wird diesplit
-Variante von Einsteigern vermutlich eher verstanden, als die mitMatcher
undPattern
. Natürlich lässt sich auch nicht alles ohnePattern
undMatcher
realisieren, weshalb diese Klassen (wie gesagt) auf der zweiten Seite angesprochen werden. Ein weiterer Vorteil bei der Verwendung von den „richtigen“ Klassen liegt darin, dass ein Performancegewinn bei komplexeren Ausdrücken erzielt wird, da die regulären Ausdrücke bereits vorkompiliert sind (wird auch auf der zweiten Seite erläutert).Letztendlich ist es wichtig beide Varianten zu kennen und ggf. auch zu können.
Gruß
Stefan
„^[\\w\\.=-]+@[\\w\\.-]+\\.[\\w]{2,4}$“;
wieso riecht ^[\\w\\.=-] aus um alle längen von Wörtern abzufangen? Müsste da nicht wie oben beschrieben ein * stehen? für mich erlaubt es ^ ein zeichen . = – in einer beliebigen reihenfolge einmal.
Hallo javauser,
zusätzlich zu dem Stern (*), gibt es auch noch das Plus (+). Dieses bedeutet, dass die vorangestellte Zeichen-Klasse beliebig oft vorkommen kann, aber mindestens einmal vorkommen muss. Bei einem Stern ist es aber auch erlaubt, dass die Klasse kein einziges Mal vorkommt.
Ich hoffe der Unterschied und die Funktionsweise ist jetzt klarer!?
Gruß
Stefan
Ps: Ich werde den Beitrag entsprechend ergänzen.