Grupos e Capturas em Regex no Java
Capture partes do texto correspondido em regex Java com parênteses, grupos numerados e grupos nomeados.
Uma expressão regular não apenas indica se uma string corresponde — ela pode dividir a correspondência em partes que você pode ler. Essas partes são chamadas de grupos. Ao envolver parte de um padrão entre parênteses, você cria um grupo de captura, e quando um Matcher obtém sucesso, você extrai cada grupo pelo número ou pelo nome. Este capítulo aborda grupos numerados, grupos nomeados, retrorreferências, grupos não capturadores e como todos eles se aplicam em substituições.
Grupos de Captura Numerados
Cada par de parênteses em um padrão abre um grupo de captura, numerado da esquerda para a direita pelo ( de abertura. O grupo 0 é especial: ele sempre representa a correspondência completa. Assim, (\d{4})-(\d{2})-(\d{2}) fornece quatro grupos — a data completa mais ano, mês e dia.
Pattern p = Pattern.compile("(\\d{4})-(\\d{2})-(\\d{2})");
Matcher m = p.matcher("2026-05-30");
if (m.matches()) {
System.out.println(m.group(0)); // 2026-05-30 (entire match)
System.out.println(m.group(1)); // 2026
System.out.println(m.group(2)); // 05
System.out.println(m.group(3)); // 30
}groupCount() retorna o número de grupos de captura excluindo o grupo 0, portanto o padrão acima reporta 3. Ler um índice de grupo que não existe lança IndexOutOfBoundsException.
Grupos Nomeados
Contar parênteses se torna frágil à medida que os padrões crescem. Grupos nomeados, escritos como (?<nome>...), permitem ler uma captura por um rótulo legível em vez de um índice. Os nomes devem ser identificadores Java válidos e únicos dentro do padrão.
Pattern p = Pattern.compile("(?<user>[\\w.]+)@(?<host>[\\w.]+)");
Matcher m = p.matcher("[email protected]");
if (m.matches()) {
System.out.println(m.group("user")); // ada
System.out.println(m.group("host")); // math.org
}Os grupos nomeados ainda são numerados internamente, portanto m.group(1) e m.group("user") retornam o mesmo texto. O nome existe apenas para facilitar a leitura.
Retrorreferências
Uma retrorreferência corresponde ao mesmo texto que um grupo anterior já capturou. Dentro do padrão você escreve \1 para o grupo 1 (ou \k<nome> para um grupo nomeado). É assim que você detecta repetições — por exemplo, uma palavra duplicada — dentro de uma única correspondência.
// \b(\w+)\s+\1\b matches a word followed by the same word again
Pattern p = Pattern.compile("\\b(\\w+)\\s+\\1\\b");
Matcher m = p.matcher("the the end");
if (m.find()) {
System.out.println(m.group(1)); // the
}Observe a barra invertida dupla no código-fonte Java: \\1 na string se torna \1 na regex real. Uma retrorreferência só pode corresponder depois que o seu grupo tiver capturado, portanto o grupo deve aparecer antes no padrão.
Capturas em Substituições
String.replaceAll, Matcher.replaceAll e appendReplacement entendem referências de grupo no texto de substituição. Use $1, $2, ... para grupos numerados e ${nome} para grupos nomeados. Isso transforma a regex em uma pequena ferramenta de reordenação e de templates.
| Referência | Significado na substituição |
|---|---|
$0 | A correspondência completa |
$1, $2, ... | Grupos de captura numerados |
${name} | Um grupo de captura nomeado |
\$ | Um sinal de dólar literal |
// Reorder "First Last" into "Last, First"
String out = "Ada Lovelace".replaceAll("(\\w+)\\s+(\\w+)", "$2, $1");
System.out.println(out); // Lovelace, AdaSe você precisar de um $ ou \ literal na saída, use o escape \\$ ou \\\\ na string Java.
Grupos Não Capturadores
Às vezes você precisa de parênteses apenas para agrupar uma alternância ou aplicar um quantificador — não para capturar. Um grupo não capturador (?:...) faz exatamente isso: agrupa sem consumir um número de grupo, o que mantém seus índices limpos e deixa o mecanismo ligeiramente mais rápido.
// Group the protocol alternation, but capture only the host
Pattern p = Pattern.compile("(?:https?|ftp)://(\\S+)");
Matcher m = p.matcher("https://w3docs.com");
if (m.find()) {
System.out.println(m.group(1)); // w3docs.com (group 1, not 2)
}Como (?:https?|ftp) é não capturador, o host é o grupo 1 e não o grupo 2. Um grupo de captura opcional que não participa da correspondência retorna null, portanto sempre verifique se grupos opcionais são nulos antes de utilizá-los.
Um Exemplo Executável
O programa abaixo exercita todos os tipos de grupos em uma única execução: partes de data numeradas, campos de e-mail nomeados, uma retrorreferência de palavra duplicada, referências de grupo em duas substituições, um grupo de protocolo não capturador e um grupo opcional que retorna null.
O que extrair da execução:
- As duas linhas de data mostram grupos numerados:
group(0)é a correspondência completa enquantogroup(1..3)são o ano, mês e dia capturados por posição. - A linha do e-mail prova que grupos nomeados leem o mesmo texto que os índices, e
groupCount=2conta apenas as capturas nomeadas, nunca o grupo 0. Doubled: theeDoubled: satvêm da retrorreferência\1correspondendo ao que o grupo de palavra acabou de capturar — cada palavra repetida é encontrada de forma independente.Swapped: Lovelace, Ada, Turing, Alanmostra$2, $1reordenando cada par de nomes, enquantoMasked: card [4111] [2222]mostra a referência nomeada${num}aplicando o template à saída.- O
(?:https?|ftp)não capturador mantém o host no grupo 1 (w3docs.com), e o grupo de fração opcional imprimefrac=nullporque nunca participou da correspondência de42.
Próximos Passos
Os grupos se baseiam no restante do mecanismo de regex, portanto é útil estar confortável com as partes ao redor:
- Pattern e Matcher — as classes cujos métodos
group(),groupCount()ereplaceAll()você chama aqui. - Quantificadores de Regex —
{4},+e?decidem quanto cada grupo captura. - Classes de Caracteres em Regex —
\d,\we\Ssão do que a maioria dos grupos é composta. - Flags de Regex — flags como
CASE_INSENSITIVEmudam o que seus grupos correspondem.