Classi Pattern e Matcher in Java
Compila pattern e confronta input in Java con le classi Pattern e Matcher.
Le espressioni regolari in Java si trovano nel package java.util.regex, e quasi tutto quel package è composto da due classi: Pattern e Matcher. Un Pattern è un'espressione regolare compilata — la regola. Un Matcher è il motore che applica quella regola a un pezzo di input e riporta ciò che ha trovato. Separare i due consente di compilare un'espressione una volta sola e riutilizzarla su migliaia di input, il che fa la differenza tra codice regex veloce e codice regex lento.
Questo capitolo tratta la compilazione di un Pattern, l'utilizzo di un Matcher, la fondamentale distinzione tra find() e matches(), i gruppi di cattura e i gruppi nominati, e i flag. Se sei nuovo alla sintassi stessa, inizia prima con i capitoli sull'introduzione alle regex e sulla sintassi delle regex.
Pattern: compila una volta, riutilizza per sempre
Pattern.compile(String regex) analizza la sintassi della regex e restituisce un Pattern immutabile e thread-safe. La compilazione è il passaggio costoso, quindi eseguila una volta sola — tipicamente in un campo static final — e condividi il risultato. I metodi di convenienza su String (matches, replaceAll, split) ricompilano il pattern a ogni chiamata, il che va bene per un uso occasionale ma è dispendioso in un ciclo.
// Good: compile once, reuse
private static final Pattern EMAIL =
Pattern.compile("[\\w.+-]+@[\\w.-]+\\.[a-z]{2,}");
// Wasteful in a loop: String.matches recompiles every iteration
for (String s : lines) {
if (s.matches("[\\w.+-]+@[\\w.-]+\\.[a-z]{2,}")) { /* ... */ }
}Nota i doppi backslash: \\w nel sorgente Java è il token regex \w, perché il backslash deve prima sopravvivere all'escaping delle stringhe di Java.
Matcher: il motore con stato
Un Pattern non contiene input né posizione — è solo la regola. Chiamare pattern.matcher(input) produce un Matcher legato a quell'input, e il Matcher porta tutto lo stato mutabile: la posizione di ricerca corrente, i limiti dell'ultimo match e i gruppi catturati. Poiché è con stato, un Matcher non è thread-safe; dai a ogni thread il suo.
| Metodo | Cosa fa |
|---|---|
matches() | Verifica se l'intero input corrisponde al pattern |
lookingAt() | Verifica se l'input corrisponde a partire dall'inizio (non deve necessariamente arrivare alla fine) |
find() | Trova la prossima corrispondenza ovunque nell'input; restituisce true e avanza |
group() / group(n) | Restituisce l'intera corrispondenza, o il gruppo di cattura n |
start() / end() | Indice del primo carattere della corrispondenza, e uno oltre l'ultimo |
replaceAll(repl) | Sostituisce ogni corrispondenza, con $1, $2 backreference ai gruppi |
reset() | Riporta il matcher alla posizione zero (opzionalmente con nuovo input) |
find() versus matches(): la confusione più comune
matches() è ancorato all'intera stringa — restituisce true solo se il pattern consuma l'intero input. find() è uno scanner: cerca il pattern ovunque e può essere chiamato ripetutamente per scorrere ogni occorrenza.
Pattern p = Pattern.compile("\\d+");
System.out.println(p.matcher("abc123").matches()); // false — whole string isn't digits
System.out.println(p.matcher("abc123").find()); // true — found "123" inside
Matcher m = p.matcher("a1 b22 c333");
while (m.find()) {
System.out.println(m.group() + " @ " + m.start()); // 1@1, 22@4, 333@8
}Un errore frequente è chiamare group() prima di un matches()/find() riuscito — questo lancia IllegalStateException, perché non c'è ancora nessuna corrispondenza da leggere.
Gruppi di cattura, gruppi nominati e sostituzione
Le parentesi in una regex creano gruppi di cattura, numerati da sinistra a destra a partire da 1 (il gruppo 0 è l'intera corrispondenza). Java supporta anche i gruppi nominati con (?<name>...), che si leggono tramite group("name") — molto più leggibile che contare le parentesi. Nelle stringhe di sostituzione, $1 e ${name} inseriscono ciò che un gruppo ha catturato. Il capitolo sui gruppi regex approfondisce i gruppi non di cattura e i backreference.
Pattern date = Pattern.compile("(?<year>\\d{4})-(?<month>\\d{2})-(?<day>\\d{2})");
Matcher m = date.matcher("2024-11-30");
if (m.matches()) {
System.out.println(m.group("year")); // 2024
System.out.println(m.group(3)); // 30 (third numbered group)
}
// Reformat using back-references
System.out.println("2024-11-30".replaceFirst(
"(\\d{4})-(\\d{2})-(\\d{2})", "$3/$2/$1")); // 30/11/2024Flag: case-insensitive, multiline e altro
Passa i flag come secondo argomento a Pattern.compile, combinati con |. I più usati sono CASE_INSENSITIVE, MULTILINE (in modo che ^/$ corrispondano alle interruzioni di riga) e DOTALL (in modo che . corrisponda anche alle newline). Gli stessi flag possono essere impostati inline con (?i), (?m), (?s). Consulta il capitolo sui flag regex per l'elenco completo e i loro compromessi.
Pattern p = Pattern.compile("error", Pattern.CASE_INSENSITIVE);
System.out.println(p.matcher("FATAL ERROR").find()); // true
// Equivalent inline form:
Pattern.compile("(?i)error");Un esempio pratico: analisi di una riga di log con un pattern compilato
Questo programma compila un pattern di data una volta sola e mette un Matcher alla prova — scansionando ogni data in una stringa con find(), confrontando find() con matches(), riformattando tramite backreference ai gruppi, suddividendo su spazi bianchi e leggendo valori da gruppi nominati.
Cosa ricavare dall'esecuzione:
find()è uno scanner con memoria. Il ciclowhile (m.find())ha individuato entrambe le date — all'indice6e23— perché ogni chiamata riprende da dove è terminata la corrispondenza precedente. Ecco come si enumerano tutte le occorrenze, ed è per questo che il totale è risultato2.matches()è tutto-o-niente.matches whole log?ha stampatofalseperché le date sono sepolte in altro testo, mentrematches one date?ha stampatotrueperché"2024-01-15"è l'intero input. Usafind()per localizzare,matches()per validare.- I gruppi numerati sono indirizzabili durante la corrispondenza. All'interno del ciclo,
m.group(1)ha restituito solo l'anno a quattro cifre (2024) mentrem.group()ha restituito l'intera data — il gruppo0è la corrispondenza, i gruppi1..nsono le catture tra parentesi da sinistra a destra. - I backreference riorganizzano il testo.
replaceAll("$3/$2/$1")ha trasformato ogniYYYY-MM-DDinDD/MM/YYYY, producendostart 15/01/2024 build 30/11/2024 done— il motore ha sostituito ogni gruppo catturato nel template di sostituzione. - I gruppi nominati si leggono come campi. Suddividendo
"a b c"su\s+le sequenze di spazi si sono ridotte a3parti, e il pattern nominato ha permesso anm.group("user")enm.group("host")di estrarrealiceew3docs.comper nome anziché per numeri di posizione fragili.