Reguläre Ausdrücke: Gruppen und Ersetzen
Kurz gesagt: Gruppen in einem regulären Ausdruck, Teile in Klammern, holen Stücke aus jedem Treffer; in Java findest du sie mit Pattern und Matcher und verwendest sie beim Ersetzen mit $1.
Problem
Der Kontoauszug
Deine Bank schickt dir einen Text mit den Zahlungen des Monats. Du willst jeden Betrag einzeln, um sie zusammenzuzählen:
String statement = "Miete 450,00 € am 01.10, Strom 87,35 € am 05.10, Wasser 23,10 € am 12.10";System.out.println(statement.matches(".*\\d+,\\d{2} €.*"));truematches sagt nur „ja, irgendwo steht ein Betrag“. Das .* auf beiden Seiten ist da, weil matches will, dass das Muster den ganzen Text abdeckt. replaceAll würde die Beträge ändern, aber es gibt sie dir nicht. Du brauchst einen Weg, jeden Treffer zu finden und ihn zu nehmen.Die Idee
Pattern und Matcher
Zum Suchen gibt es zwei Klassen aus java.util.regex:
String statement = "Miete 450,00 € am 01.10, Strom 87,35 € am 05.10, Wasser 23,10 € am 12.10";Pattern amount = Pattern.compile("\\d+,\\d{2} €");Matcher m = amount.matcher(statement);while (m.find()) { System.out.println(m.group());}450,00 €87,35 €23,10 €Pattern.compile(...)macht aus dem Muster einPattern-Objekt. Mach das einmal und verwende es so oft du willst.amount.matcher(text)liefert einenMatcher: einen Sucher für dieses Muster in diesem Text.m.find()findet den nächsten Treffer. Es gibttruezurück, wenn es einen gibt, undfalse, wenn keine mehr da sind. Deshalb steht es in einemwhile.m.group()ist der Text des Treffers, der gerade gefunden wurde.
Oben in der Datei brauchst du import java.util.regex.Matcher; und import java.util.regex.Pattern;.
Genauer, für Neugierige
String.matches,replaceAllundsplitrufen in Wirklichkeit jedes MalPattern.compileauf. In einer Schleife über Tausende Zeilen ist es schneller, das Muster einmal zu kompilieren, zum Beispiel in einem Feldstatic final Pattern.m.matches()prüft den ganzen Text, wieString.matches.m.lookingAt()prüft nur, ob der Text mit einem Treffer beginnt.
Sieh es dir an
Wo der Treffer steht
Außer dem Text weiß der Matcher auch, wo er gefunden wurde:
String log = "ERROR Festplatte; INFO Start; ERROR Netzwerk";Matcher m = Pattern.compile("ERROR").matcher(log);int count = 0;while (m.find()) { count++; System.out.println(m.group() + " von " + m.start() + " bis " + m.end());}System.out.println("Gesamt: " + count);ERROR von 0 bis 5ERROR von 30 bis 35Gesamt: 2start() ist der Index des ersten Zeichens des Treffers, und end() ist der Index nach dem letzten, wie bei substring. Deshalb ist log.substring(m.start(), m.end()) dasselbe wie m.group().Probier es aus
Eine Ziffer oder eine Zahl?
Was gibt das Programm aus?
Matcher m = Pattern.compile("\\d+").matcher("Tisch 12, 4 Personen, 2 Flaschen");while (m.find()) { System.out.print("[" + m.group() + "]");}System.out.println();\d+ heißt „eine oder mehrere Ziffern“, also ist „12“ ein Treffer. Mit \d ohne + wäre jede Ziffer einzeln: [1][2][4][2]. Das Leerzeichen und das Komma unterbrechen die Folge, deshalb verschmelzen 12 und 4 nicht.
Die Idee
Gruppen: Teile des Treffers
Klammern im Muster bilden eine Gruppe. Der Matcher merkt sich für jede Gruppe einzeln, was auf sie gepasst hat:
Pattern date = Pattern.compile("(\\d{2})\\.(\\d{2})\\.(\\d{4})");Matcher m = date.matcher("Fällig: 15.11.2026");if (m.find()) { System.out.println(m.group()); System.out.println(m.group(1) + " / " + m.group(2) + " / " + m.group(3)); System.out.println(m.group(3) + "-" + m.group(2) + "-" + m.group(1));}15.11.202615 / 11 / 20262026-11-15- Die Gruppen werden nach ihren öffnenden Klammern nummeriert, von links nach rechts, ab 1.
group(0)odergroup()ist der ganze Treffer.if (m.find())ist für den Fall, dass du nur den ersten Treffer brauchst.
So nimmst du aus einem Datum Tag, Monat und Jahr einzeln heraus und ordnest sie so an, wie du sie brauchst.
Pattern date = Pattern.compile("(\\d{2})\\.(\\d{2})\\.(\\d{4})");Matcher m = date.matcher("Fällig: 15.11.2026");if (m.find()) { System.out.println(m.group()); System.out.println(m.group(1) + " / " + m.group(2) + " / " + m.group(3)); System.out.println(m.group(3) + "-" + m.group(2) + "-" + m.group(1));}Schritt für Schritt, in Worten
- Das Datumsmuster hat drei Klammerpaare: drei Gruppen.
- m sucht dieses Muster in diesem Text.
- find() findet das Datum: von Index 8 bis 18.
- Jede Gruppe merkt sich ihren Teil: Gruppe 1 ist 15, Gruppe 2 ist 11, Gruppe 3 ist 2026.
- Die Teile liegen in m, jeder unter seiner Nummer. group(0) ist der ganze Treffer.
- group() ist dasselbe wie group(0): 15.11.2026.
- Die drei Gruppen, eine nach der anderen.
- In umgekehrter Reihenfolge ergeben die Teile 2026-11-15.
Genauer, für Neugierige
(?:...)ist eine Gruppe, die die Teile nur zusammenhält, ohne sie sich zu merken. Zum Beispiel bildet(?:\+359|0)8[789]\d{7}keine Gruppe 1.
m.groupCount()liefert die Anzahl der Gruppen im Muster.- Wenn eine Gruppe am Treffer nicht beteiligt war (zum Beispiel, weil
(,\d{2})?fehlte), istm.group(1)gleichnull.
Die Idee
Benannte Gruppen und Ersetzen
(?<name>...), und dann m.group("name"). Hier ist eine Zeile von einer Café-Rechnung:Pattern item = Pattern.compile("(?<qty>\\d+) x (?<name>[A-Za-z ]+) zu (?<price>\\d+\\.\\d{2})");Matcher m = item.matcher("2 x Kaffee mit Milch zu 3.20; 1 x Croissant zu 2.90");while (m.find()) { int qty = Integer.parseInt(m.group("qty")); double price = Double.parseDouble(m.group("price")); System.out.println(m.group("name") + ": " + qty * price + " €");}Kaffee mit Milch: 6.4 €Croissant: 2.9 €replaceAll: Im Ersetzungstext ist $1 die Gruppe 1, und ${name} ist die Gruppe mit diesem Namen. Hier die Daten von der deutschen Form in die ISO-Form und in die amerikanische:String text = "Frist: 15.11.2026, Erinnerung: 08.11.2026";System.out.println(text.replaceAll("(\\d{2})\\.(\\d{2})\\.(\\d{4})", "$3-$2-$1"));System.out.println(text.replaceAll("(?<d>\\d{2})\\.(?<m>\\d{2})\\.(?<y>\\d{4})", "${m}/${d}/${y}"));Frist: 2026-11-15, Erinnerung: 2026-11-08Frist: 11/15/2026, Erinnerung: 11/08/2026Pattern und Matcher nicht: String.replaceAll versteht Gruppen von selbst.Genauer, für Neugierige
$ist im Ersetzungstext ein Sonderzeichen. Wenn du ein echtes Dollarzeichen willst, schreib\\$, oder gib den Text durchMatcher.quoteReplacement(...), wenn er vom Benutzer kommt.
- Du kannst eine Gruppe auch im Muster selbst verwenden:
\1heißt „dasselbe wie Gruppe 1“. So fängst du wiederholte Wörter. Sieh dir die Karte zu den Flags an.
Probier es aus
Wie viel nimmt +?
Du willst alle HTML-Tags aus der Zeile:
Was gibt das Programm aus?
String html = "<b>Preis:</b> <i>12 €</i>";Matcher m = Pattern.compile("<.+>").matcher(html);while (m.find()) { System.out.println(m.group());}Ein Treffer, die ganze Zeile. .+ nimmt so viel, wie es kann: Es geht bis zum Ende des Textes und gibt dann Zeichen für Zeichen zurück, bis es ein > findet. Das erste > von hinten ist das letzte in der Zeile. Solche Quantifizierer heißen gierig.
Die Idee
Gierig und genügsam
Ein ? nach einem Quantifizierer macht ihn genügsam: Er nimmt so wenig, wie er kann, und hält bei der ersten Gelegenheit an.
String html = "<b>Preis:</b> <i>12 €</i>";Matcher m = Pattern.compile("<.+?>").matcher(html);while (m.find()) { System.out.println(m.group());}System.out.println(html.replaceAll("<[^>]+>", ""));<b></b><i></i>Preis: 12 €<.+?>heißt „<, mindestens ein Zeichen und das erste>danach“.<[^>]+>macht dasselbe auf andere Weise: „<, Zeichen, die nicht>sind, und>“. Es kann kein>überspringen, deshalb ist es auch schneller. Damit entfernst du die Tags, und nur der Text bleibt übrig.
| Gierig | Genügsam |
|---|---|
* |
*? |
+ |
+? |
? |
?? |
{2,5} |
{2,5}? |
Probier es aus
Finde den Fehler
Das Programm soll die drei Bestellnummern ausgeben, gibt aber nur #1042 aus. In welcher Zeile ist der Fehler?
Wähle die Zeile mit dem Fehler.
if ruft find() einmal auf und findet nur den ersten Treffer. Für alle brauchst du while (m.find()): Jeder Aufruf macht dort weiter, wo der vorige aufgehört hat.Die Idee
Flags und Zeilen
Die Flags gibst du als zweites Argument von Pattern.compile an, verbunden mit |. Aus einer Logdatei mit mehreren Zeilen holst du die Fehlermeldungen, egal ob groß oder klein geschrieben:
String log = "INFO: Start\nERROR: Keine Verbindung zur Datenbank\nINFO: Neuer Versuch\nerror: Festplatte 95%";Pattern p = Pattern.compile("^error: (.+)$", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);Matcher m = p.matcher(log);while (m.find()) { System.out.println(m.group(1));}Keine Verbindung zur DatenbankFestplatte 95%Pattern.CASE_INSENSITIVEist dasselbe wie(?i): Groß- und Kleinbuchstaben gelten als gleich.Pattern.MULTILINEmacht^und$zum Anfang und Ende jeder Zeile. Ohne es sind sie nur Anfang und Ende des ganzen Textes, und das Muster findet nichts.Pattern.DOTALLlässt.auch einen Zeilenumbruch fangen.
Genauer, für Neugierige
Zwei Dinge, denen du in fremden Mustern begegnen wirst:
- Eine Rückreferenz (backreference)
\1: „derselbe Text wie Gruppe 1“. So fängst du wiederholte Wörter. - Ein Lookahead
(?=...): „irgendwo ab hier kommt ...“, ohne Zeichen zu verbrauchen. Mehrere davon hintereinander sind praktisch für Passwörter: „hat eine Ziffer“ und „hat einen Großbuchstaben“, und mindestens 8 Zeichen.
Pattern twice = Pattern.compile("(?U)\\b(\\w+) \\1\\b");Matcher m = twice.matcher("Die Zahlung für für März ist eingegangen, bitte bitte prüfe sie.");while (m.find()) { System.out.println("Wiederholt: " + m.group(1));}String strong = "(?=.*\\d)(?=.*[A-Z]).{8,}";System.out.println("geheim12".matches(strong) + " " + "Geheim12".matches(strong));Wiederholt: fürWiederholt: bittefalse true(?!...) ist das Gegenteil: „ab hier kommt kein ...“. Es gibt auch Lookbehind: (?<=...) und (?<!...).
Übung
Finden, herausholen, ersetzen
Löse die Aufgaben in deinem Editor. Der Text ist eine Zeile von der Tastatur. In der Lektion „Dateien“ machst du sie auch mit einer Datei.
1. Suchen und ersetzen. Ersetze das Wort „alt“ überall, wo es ein ganzes Wort ist, durch „neu“, und sag, wie viele Ersetzungen du gemacht hast. Teile anderer Wörter („altmodisch“) bleiben unverändert.
Das Gerät ist alt. Auch das Sofa ist alt, aber nicht altmodisch.Das Gerät ist neu. Auch das Sofa ist neu, aber nicht altmodisch.Ersetzt: 22. Wörter mit einem Präfix. In der ersten Zeile steht das Präfix, in der zweiten der Text. Gib jedes Wort aus, das mit dem Präfix beginnt.
aufaufräumen aufstehen Laufband ablaufen aufgebenaufräumenaufstehenaufgeben3. Adressen im Text. Hol alle Webadressen heraus, die mit http:// oder https:// beginnen. Der Punkt am Satzende gehört nicht zur Adresse.
Siehe die Seite https://www.example.com und diese: http://www.test.com. Die Dokumente liegen unter https://docs.example.com/guide/start.https://www.example.comhttp://www.test.comhttps://docs.example.com/guide/start4. Daten nach ISO. Mach aus jedem Datum tt.mm.jjjj im Text die Form jjjj-mm-tt, mit einem einzigen replaceAll.
Vertrag vom 03.10.2026, Zahlung bis 15.10.2026.Vertrag vom 2026-10-03, Zahlung bis 2026-10-15.Weitere Aufgaben
- Summe des Kontoauszugs. Nimm die Beträge aus der ersten Karte mit einer Gruppe für die Euro und einer Gruppe für die Cent, zähl sie zusammen und gib „Summe: 560,45 €“ aus.
- Hashtags. Hol alle Wörter, die mit
#beginnen, aus einem Beitrag, in dem Wörter mit und ohne Umlaute (ä, ö, ü, ß) gemischt sind. Überleg, warum du(?U)brauchst. - Ohne Tags. Entferne die HTML-Tags aus
<p>Die Frist ist der <b>15.11</b>.</p>und gib den reinen Text aus. - Telefonnummern im Text. Finde alle Handynummern in einem Text und gib sie in der Form
+359 88 812 3456aus, mit Gruppen undreplaceAll.
Test
Teste dich
Verwandte Lektionen
Abschnitt „Verwandte Lektionen“- Baut auf: Instanzmethoden · static: was alle Objekte teilen · Strings · Reguläre Ausdrücke
- Aufgaben zum Thema: Text und Dateien