W3docs

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.

MetodoCosa 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/2024

Flag: 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.

java— editable, runs on the server

Cosa ricavare dall'esecuzione:

  • find() è uno scanner con memoria. Il ciclo while (m.find()) ha individuato entrambe le date — all'indice 6 e 23 — perché ogni chiamata riprende da dove è terminata la corrispondenza precedente. Ecco come si enumerano tutte le occorrenze, ed è per questo che il totale è risultato 2.
  • matches() è tutto-o-niente. matches whole log? ha stampato false perché le date sono sepolte in altro testo, mentre matches one date? ha stampato true perché "2024-01-15" è l'intero input. Usa find() 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) mentre m.group() ha restituito l'intera data — il gruppo 0 è la corrispondenza, i gruppi 1..n sono le catture tra parentesi da sinistra a destra.
  • I backreference riorganizzano il testo. replaceAll("$3/$2/$1") ha trasformato ogni YYYY-MM-DD in DD/MM/YYYY, producendo start 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 a 3 parti, e il pattern nominato ha permesso a nm.group("user") e nm.group("host") di estrarre alice e w3docs.com per nome anziché per numeri di posizione fragili.

Esercizio

Pratica
Chiami 'pattern.matcher(input)' per ottenere un Matcher, poi chiami subito 'matcher.group()' senza aver chiamato prima 'find()' o 'matches()'. Cosa succede?
Chiami 'pattern.matcher(input)' per ottenere un Matcher, poi chiami subito 'matcher.group()' senza aver chiamato prima 'find()' o 'matches()'. Cosa succede?
Was this page helpful?