Quantificadores de Regex em Java
Como os quantificadores de regex Java (*, +, ?, {n,m}) se comportam nos modos greedy, relutante e possessivo.
Um quantificador define quantas vezes o elemento anterior pode se repetir. * significa "zero ou mais", + significa "um ou mais", ? significa "zero ou um", e {n,m} define um intervalo exato. Isso é comum a todos os flavors de regex. O que confunde as pessoas em Java é que cada quantificador existe em três modos — greedy, relutante e possessivo — e o modo, não a contagem, determina como o motor de regex em java.util.regex percorre a entrada.
Esta página aborda os quatro quantificadores básicos, os três modos de correspondência, por que padrões greedy fazem correspondências excessivas e como os quantificadores possessivos protegem contra backtracking catastrófico. Assume-se que você já sabe como compilar um Pattern e executar um Matcher; caso contrário, comece com Pattern e Matcher. Para os metacaracteres que você irá repetir, veja classes de caracteres, e para capturar o texto repetido, veja grupos.
Os quatro quantificadores básicos
Anexe um quantificador a um único caractere, uma classe de caracteres ou um grupo, e ele controla a repetição do elemento imediatamente à sua esquerda:
| Quantificador | Repete o elemento anterior | Exemplo | Corresponde a |
|---|---|---|---|
* | zero ou mais vezes | ab*c | ac, abc, abbbc |
+ | uma ou mais vezes | ab+c | abc, abbc (não ac) |
? | zero ou uma vez | colou?r | color, colour |
{n} | exatamente n vezes | \d{4} | um ano com 4 dígitos |
{n,} | n ou mais vezes | \d{2,} | dois ou mais dígitos |
{n,m} | entre n e m vezes | \d{3,5} | 3 a 5 dígitos |
Pattern.matches("ab*c", "abbbc"); // true — three b's
Pattern.matches("ab+c", "ac"); // false — '+' needs at least one b
Pattern.matches("colou?r", "color");// true — the 'u' is optional
Pattern.matches("\\d{3,5}", "1234");// true — four digits is within 3..5Greedy é o padrão
Um quantificador simples é greedy: ele consome o máximo possível da entrada e depois faz backtracking — devolvendo caracteres um a um — até que o restante do padrão possa corresponder. É por isso que um <.+> ingênuo contra HTML consome muito mais do que uma tag:
String html = "<b>one</b>";
Matcher m = Pattern.compile("<.+>").matcher(html);
m.find();
m.group(); // "<b>one</b>" — '.+' ate everything, then backed up to the last '>'O motor primeiro capturou a string inteira, não encontrou > no final e caminhou para trás até encontrar um > — parando no último deles.
Relutante: adicione ? para capturar o mínimo possível
Acrescente ? a qualquer quantificador (*?, +?, ??, {n,m}?) e ele se torna relutante (também chamado de lazy): ele corresponde ao menor número de repetições primeiro e expande apenas quando forçado. É o que você geralmente quer ao varrer tokens delimitados:
String html = "<b>one</b>";
Matcher m = Pattern.compile("<.+?>").matcher(html);
m.find();
m.group(); // "<b>" — stopped at the first '>'Mesmo padrão, um caractere a mais, comportamento oposto: o greedy <.+> retorna a string inteira enquanto o relutante <.+?> retorna apenas a primeira tag.
Possessivo: adicione + e nunca devolva
Acrescente + (*+, ++, ?+, {n,m}+) e o quantificador se torna possessivo: ele captura o máximo possível como um greedy, mas recusa-se a fazer backtracking. Se o restante do padrão falhar, a correspondência inteira falha — não há retrocesso para resgatá-la.
// Possessive '.++' eats the final '>' too and won't return it, so no '>' is left
Pattern.compile("<.++>").matcher("<b>one</b>").find(); // falsePor que abrir mão dessa flexibilidade? Velocidade e segurança. Como um quantificador possessivo nunca reconsidera, ele não pode cair em backtracking catastrófico — o colapso exponencial que congela uma thread em padrões como (a+)+b alimentado com uma longa sequência de a sem b. O possessivo a++b responde "sem correspondência" quase instantaneamente.
| Modo | Sintaxe | Estratégia | Faz backtracking? |
|---|---|---|---|
| Greedy | X* | captura o máximo, depois recua conforme necessário | sim |
| Relutante | X*? | captura o mínimo, depois expande conforme necessário | sim |
| Possessivo | X*+ | captura o máximo e mantém | não |
Um exemplo prático: os três modos lado a lado
Este programa executa a mesma entrada com quantificadores greedy, relutante e possessivo, depois exercita o intervalo {n,m} e um quantificador de grupo. Tudo aqui é java.util.regex puro do JDK.
O que extrair da execução:
- O greedy
<.+>exibiu<b>one</b><i>two</i>— a string inteira. Ele consumiu tudo e depois recuou até o último>, o que explica exatamente por que padrões greedy fazem correspondências excessivas entre delimitadores. - O relutante
<.+?>exibiu<b>com a mesma entrada. O simples?inverteu a estratégia de "máximo" para "mínimo", parando no primeiro>— a correção para varrer tag por tag. - O possessivo
<.++>exibiumatches=false. Ele engoliu o>final e recusou-se a devolvê-lo, então o>no final do padrão não tinha com o que corresponder e toda a tentativa falhou — o preço de nunca fazer backtracking. \d{3,5}rejeitou12(no match, dígitos insuficientes), aceitou123e12345inteiros, e em1234567correspondeu apenas a12345(len 5) — o limite superior5o restringiu mesmo com mais dígitos disponíveis.- O padrão de grupo
(\w+\s*){2,3}correspondeu aalpha beta gamma— três palavras, seu máximo — comprovando que o quantificador se aplicou ao grupo entre parênteses inteiro, ea++bretornoufalseinstantaneamente em uma longa sequência deasemb, mostrando como os quantificadores possessivos evitam o backtracking catastrófico.
Qual modo devo usar?
- Prefira o relutante (
*?,+?) ao corresponder conteúdo entre delimitadores — aspas, tags, colchetes, blocos delimitados. Ele para no primeiro delimitador de fechamento em vez do último, que é quase sempre o que você quer dizer. - Mantenha o greedy (o padrão) quando você genuinamente deseja a correspondência mais longa possível, ou quando há apenas uma correspondência possível de qualquer forma e o
?/+extra seria apenas ruído. - Use o possessivo (
*+,++) como uma ferramenta de desempenho e segurança em entradas que você não controla. Por nunca fazer backtracking, ele não pode desencadear backtracking catastrófico, mas também falhará em correspondências que um quantificador greedy teria resgatado — portanto, aplique-o apenas onde você sabe que o backtracking é desnecessário.
Uma correção comum no mundo real: um regex que funciona em entradas pequenas mas trava em entradas grandes geralmente tem um quantificador greedy dentro de um grupo, como (\w+)+. Tornar o quantificador interno possessivo ((\w++)+ ou reestruturando o padrão) elimina o colapso exponencial.