Classes Pattern e Matcher do Java
Compile padrões e encontre correspondências em Java com as classes Pattern e Matcher.
As expressões regulares em Java residem no pacote java.util.regex, e quase todo esse pacote se resume a duas classes: Pattern e Matcher. Um Pattern é uma expressão regular compilada — a regra. Um Matcher é o motor que executa essa regra contra um trecho de entrada e relata o que encontrou. Separar os dois permite compilar uma expressão uma única vez e reutilizá-la em milhares de entradas, o que faz a diferença entre código de regex rápido e código de regex lento.
Este capítulo aborda a compilação de um Pattern, o uso de um Matcher, a crucial distinção entre find() e matches(), grupos de captura e grupos nomeados, e flags. Se você é novo na sintaxe em si, comece pelos capítulos de introdução a regex e sintaxe de regex primeiro.
Pattern: compile uma vez, reutilize para sempre
Pattern.compile(String regex) analisa a sintaxe do regex e retorna um Pattern imutável e thread-safe. A compilação é a etapa custosa, então faça-a uma vez — tipicamente em um campo static final — e compartilhe o resultado. Os métodos de conveniência de String (matches, replaceAll, split) recompilam o padrão a cada chamada, o que é aceitável para uso avulso, mas desperdiçador em um laço.
// 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,}")) { /* ... */ }
}Observe as barras invertidas duplicadas: \\w no código-fonte Java é o token de regex \w, pois a barra invertida precisa primeiro sobreviver ao escape próprio de strings do Java.
Matcher: o motor com estado
Um Pattern não guarda entrada nem posição — é apenas a regra. Chamar pattern.matcher(input) produz um Matcher vinculado àquela entrada, e o Matcher carrega todo o estado mutável: a posição de busca atual, os limites da última correspondência e os grupos capturados. Por ser stateful, um Matcher não é thread-safe; dê a cada thread o seu próprio.
| Método | O que faz |
|---|---|
matches() | Testa se a entrada inteira corresponde ao padrão |
lookingAt() | Testa se a entrada corresponde a partir do início (não precisa alcançar o final) |
find() | Encontra a próxima correspondência em qualquer parte da entrada; retorna true e avança |
group() / group(n) | Retorna a correspondência inteira, ou o grupo de captura n |
start() / end() | Índice do primeiro caractere da correspondência e um após o último |
replaceAll(repl) | Substitui cada correspondência, com referências retroativas $1, $2 para grupos |
reset() | Rebobina o matcher para a posição zero (opcionalmente com nova entrada) |
find() versus matches(): a confusão mais comum
matches() está ancorado à string inteira — retorna true apenas se o padrão consumir toda a entrada. find() é um scanner: procura o padrão em qualquer lugar e pode ser chamado repetidamente para percorrer cada ocorrência.
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
}Um erro frequente é chamar group() antes de um matches()/find() bem-sucedido — isso lança IllegalStateException, pois não há correspondência para ler ainda.
Grupos de captura, grupos nomeados e substituição
Parênteses em um regex criam grupos de captura, numerados da esquerda para a direita começando em 1 (grupo 0 é a correspondência inteira). Java também suporta grupos nomeados com (?<name>...), que você recupera via group("name") — muito mais legível do que contar parênteses. Em strings de substituição, $1 e ${name} inserem o que um grupo capturou. O capítulo de grupos de regex aprofunda grupos não capturantes e referências retroativas.
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/2024Flags: insensível a maiúsculas, multilinha e mais
Passe flags como segundo argumento para Pattern.compile, combinadas com |. As mais usadas são CASE_INSENSITIVE, MULTILINE (para que ^/$ correspondam em quebras de linha) e DOTALL (para que . também corresponda a quebras de linha). As mesmas flags podem ser definidas inline com (?i), (?m), (?s). Veja o capítulo de flags de regex para a lista completa e seus prós e contras.
Pattern p = Pattern.compile("error", Pattern.CASE_INSENSITIVE);
System.out.println(p.matcher("FATAL ERROR").find()); // true
// Equivalent inline form:
Pattern.compile("(?i)error");Um exemplo prático: analisando uma linha de log com um padrão compilado
Este programa compila um padrão de data uma vez e coloca um Matcher à prova — varrendo toda data em uma string com find(), contrastando find() com matches(), reformatando via referências retroativas de grupo, dividindo em espaços em branco e lendo valores de grupos nomeados.
O que observar na execução:
find()é um scanner com memória. O laçowhile (m.find())localizou ambas as datas — no índice6e no índice23— porque cada chamada retoma de onde a correspondência anterior terminou. É assim que você enumera cada ocorrência, e é por isso que o total resultou em2.matches()é tudo ou nada.matches whole log?imprimiufalseporque as datas estão enterradas em outro texto, enquantomatches one date?imprimiutrueporque"2024-01-15"é toda a entrada. Usefind()para localizar,matches()para validar.- Grupos numerados são acessíveis durante a correspondência. Dentro do laço,
m.group(1)retornou apenas o ano de quatro dígitos (2024) enquantom.group()retornou a data inteira — grupo0é a correspondência, grupos1..nsão as capturas entre parênteses da esquerda para a direita. - Referências retroativas reorganizam o texto.
replaceAll("$3/$2/$1")transformou cadaYYYY-MM-DDemDD/MM/YYYY, produzindostart 15/01/2024 build 30/11/2024 done— o motor substituiu cada grupo capturado no template de substituição. - Grupos nomeados se leem como campos. Dividir
"a b c"em\s+colapsou as sequências de espaços em3partes, e o padrão nomeado permitiu quenm.group("user")enm.group("host")extraíssemaliceew3docs.compor nome em vez de por posição numérica frágil.