Expressões Regulares (Regex) em Java
Expressões regulares (Regex) são padrões que descrevem um conjunto de strings. Elas são uma ferramenta extremamente poderosa para validação, busca, extração e manipulação de texto de forma avançada. Antes de mergulhar no código Java, é crucial entender os blocos que formam uma expressão regular.
Os Blocos de Construção das Expressões Regulares
Toda Regex é construída a partir de uma combinação de caracteres literais e metacaracteres.
Componente | Símbolos Comuns | Descrição | Exemplo |
---|---|---|---|
Caracteres Literais | a , B , 1 , ! | Qualquer caractere sem significado especial. Ele corresponde a si mesmo. | gato corresponde a "gato" |
Metacaracteres | \d , \w , \s , . | Atalhos para conjuntos comuns de caracteres.\d : Qualquer dígito (0-9).\w : Qualquer caractere de palavra (a-z, A-Z, 0-9, _ ).\s : Qualquer caractere de espaço em branco.. : Qualquer caractere (exceto nova linha). | \d\d corresponde a "25" |
Quantificadores | * , + , ? , {n} | Controlam quantas vezes o elemento anterior pode aparecer.* : Zero ou mais vezes.+ : Uma ou mais vezes.? : Zero ou uma vez (opcional).{n} : Exatamente n vezes. | \d{3} corresponde a "123" |
Classes de Caracteres | [abc] , [a-z] | Correspondem a um único caractere de um conjunto definido.[abc] : Corresponde a 'a', 'b', ou 'c'.[a-z] : Corresponde a qualquer letra minúscula. | gr[ae]y corresponde a "gray" e "grey" |
Grupos de Captura | (...) | Agrupam partes do padrão. São essenciais para extrair subconjuntos específicos do texto que correspondeu. | (\d{2}) captura os dois dígitos |
Âncoras | ^ , $ | Indicam posições.^ : Início da string.$ : Fim da string. | ^Fim corresponde a "Fim" apenas no início da string. |
Lembre-se: em uma
String
Java, a barra invertida\
é um caractere especial. Portanto, para que o motor Regex veja um metacaractere como\d
, você precisa escrevê-lo no código como"\\d"
.
Como Usar Regex em Java
Existem duas abordagens principais para trabalhar com Regex em Java.
Abordagem 1: Métodos Convenientes da Classe String
Para tarefas simples e pontuais, a própria classe String
oferece métodos que utilizam expressões regulares internamente.
matches()
Este método retorna true
somente se a string inteira corresponder exatamente à expressão regular.
String input = "+12343454556";
// Verifica um '+' opcional no início, seguido por exatamente 11 dígitos.
boolean result = input.matches("\\+?\\d{11}");
if (result) {
System.out.println("É um formato de telefone válido.");
}
split()
Este método divide uma string em um array com base em um delimitador definido por uma Regex.
String text = "FIFA will never regret it, not even for a second.";
// O regex divide a string por um ou mais espaços, vírgulas ou pontos.
String[] words = text.split("[\\s,.]+");
for (String word : words) {
System.out.println(word);
}
O resultado será:
FIFA will never regret it not even for a second
Abordagem 2: Controle Total com Pattern
e Matcher
Para controle completo, maior performance em operações repetitivas e funcionalidades avançadas, o Java oferece o pacote java.util.regex
.
Pattern
: Representa a expressão regular de forma compilada e otimizada. Compile uma vez, reutilize várias vezes.Matcher
: O "motor" que executa as operações de busca em uma string de entrada, utilizando umPattern
compilado.
O fluxo de regex é:
- Compilar o Regex: Crie um objeto
Pattern
comPattern.compile("seu_regex")
. - Criar o
Matcher
: Obtenha um objetoMatcher
compattern.matcher("string_de_entrada")
. - Executar a Busca: Utilize métodos como
find()
para encontrar correspondências.
Encontrando Todas as Ocorrências
Este é o uso mais comum e poderoso do Matcher
.
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexApp {
public static void main(String[] args) {
String input = "Hello Java! Hello JavaScript! JavaSE 8.";
Pattern pattern = Pattern.compile("Java(\\w*)");
Matcher matcher = pattern.matcher(input);
// O laço while(matcher.find()) é a forma canônica de iterar por todas as correspondências.
while (matcher.find()) {
System.out.println("Correspondência: " + matcher.group(0) + " | Grupo: " + matcher.group(1));
}
}
}
Resultado:
Correspondência: Java | Grupo: Correspondência: JavaScript | Grupo: Script Correspondência: JavaSE | Grupo: SE
O método matcher.group()
(ou group(0)
) retorna a correspondência inteira. matcher.group(1)
retorna o texto capturado pelo primeiro par de parênteses (...)
no regex.
Modificadores de Modo (Flags)
Agora que conhecemos o Pattern
, podemos explorar como modificar seu comportamento. A necessidade mais comum é fazer uma busca que ignore maiúsculas e minúsculas (case-insensitive). Existem duas maneiras de fazer isso:
1. Usando um Marcador Inline (?i)
:
Você pode inserir o marcador (?i)
diretamente no início da sua expressão regular.
// O (?i) faz com que "java" corresponda a "Java", "java", "JAVA", etc.
Pattern patternCI = Pattern.compile("(?i)java(\\w*)");
Matcher matcherCI = patternCI.matcher("Hello java! Hello JAVA!");
while (matcherCI.find()) {
System.out.println("Encontrado: " + matcherCI.group());
}
2. Usando uma Flag no Método compile()
:
A forma mais comum e recomendada de fazer isso é passar um segundo argumento para o método compile()
.
// O segundo argumento Pattern.CASE_INSENSITIVE ativa o modo case-insensitive.
Pattern patternFlag = Pattern.compile("java(\\w*)", Pattern.CASE_INSENSITIVE);
Matcher matcherFlag = patternFlag.matcher("Hello java! Hello JAVA!");
while (matcherFlag.find()) {
System.out.println("Encontrado: " + matcherFlag.group());
}
Ambas as formas produzem o mesmo resultado. A segunda abordagem é frequentemente preferida por ser mais explícita e legível no código Java.
Quando Usar Cada Abordagem?
Use os métodos da classe
String
(matches
,split
) quando:- A operação é simples e realizada apenas uma vez (não está em um loop).
- A simplicidade do código é mais importante que a performance.
Use
Pattern
eMatcher
quando:- O mesmo padrão de Regex precisa ser aplicado várias vezes (dentro de um loop). Compilar o
Pattern
uma vez é muito mais eficiente. - Você precisa de funcionalidades avançadas, como encontrar múltiplas ocorrências (
find()
), capturar grupos específicos (group(n)
) ou obter os índices de uma correspondência.
- O mesmo padrão de Regex precisa ser aplicado várias vezes (dentro de um loop). Compilar o
Para uma lista exaustiva de todas as construções de Regex suportadas pela implementação Java, a documentação oficial da classe Pattern
é a referência definitiva.
📝 Exercícios
Tarefa 1: Validar um CEP
Descrição: Complete o método validarCep
para que ele retorne true
se a string de entrada corresponder exatamente ao formato de CEP brasileiro (XXXXX-XXX
), e false
caso contrário.
Código inicial:
public class Main {
public static void main(String[] args) {
System.out.println("Testando '01001-000': " + validarCep("01001-000")); // Deve retornar true
System.out.println("Testando '99999-999': " + validarCep("99999-999")); // Deve retornar true
System.out.println("Testando '12345-abc': " + validarCep("12345-abc")); // Deve retornar false
System.out.println("Testando '1234-5678': " + validarCep("1234-5678")); // Deve retornar false
}
static boolean validarCep(String cep) {
// Use o método String.matches() com a expressão regular apropriada.
// O regex deve exigir 5 dígitos, um hífen, e depois 3 dígitos.
}
}
Resposta
public class Main {
public static void main(String[] args) {
System.out.println("Testando '01001-000': " + validarCep("01001-000")); // Deve retornar true
System.out.println("Testando '99999-999': " + validarCep("99999-999")); // Deve retornar true
System.out.println("Testando '12345-abc': " + validarCep("12345-abc")); // Deve retornar false
System.out.println("Testando '1234-5678': " + validarCep("1234-5678")); // Deve retornar false
}
static boolean validarCep(String cep) {
return cep.matches("\\d{5}-\\d{3}");
}
}
\\d{5}-\\d{3}
é ideal para esta validação:
\\d{5}
: Corresponde a exatamente 5 ocorrências de um dígito (\d
).-
: Corresponde ao caractere literal de hífen.\\d{3}
: Corresponde a exatamente 3 ocorrências de um dígito.
O método matches()
garante que a string inteira obedeça a este padrão.Tarefa 2: Dividir uma Lista de Tags
Descrição: Você recebeu uma string contendo várias tags separadas por vírgula e espaços. Use o método String.split()
para dividir a string em um array de tags individuais, removendo os delimitadores.
Código inicial:
public class Main {
public static void main(String[] args) {
String listaDeTags = "java, regex, programação, desenvolvimento";
String[] tags = null;
// Use o método split para preencher o array "tags".
// O delimitador pode ser uma vírgula seguida por espaços opcionais.
// Este loop irá imprimir cada tag do array.
for (String tag : tags) {
System.out.println("Tag: " + tag);
}
}
}
Resposta
public class Main {
public static void main(String[] args) {
String listaDeTags = "java, regex, programação, desenvolvimento";
// O regex ",\\s*" divide pela vírgula seguida por zero ou mais espaços.
String[] tags = listaDeTags.split(",\\s*");
for (String tag : tags) {
System.out.println("Tag: " + tag);
}
}
}
split()
usa a expressão regular fornecida como delimitador.
,
: Corresponde ao caractere literal de vírgula.\\s*
: Corresponde a zero ou mais (*
) ocorrências de um caractere de espaço em branco (\s
).
Isso garante que a divisão funcione tanto para "java,regex"
quanto para "java, regex"
, resultando em um array limpo.Tarefa 3: Encontrar Todos os Preços em um Texto
Descrição: Use as classes Pattern
e Matcher
para encontrar e imprimir todos os valores de preços (no formato R$XX.XX
) contidos em um texto.
Código inicial:
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Main {
public static void main(String[] args) {
String texto = "O produto A custa R$23.50, o produto B custa R$19.99 e a taxa é de R$5.00.";
// Crie o Pattern e o Matcher.
// O regex deve encontrar "R$" seguido por dígitos, um ponto, e mais dois dígitos.
// Use um laço while(matcher.find()) para imprimir todas as correspondências.
System.out.println("Preços encontrados:");
}
}
Resposta
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Main {
public static void main(String[] args) {
String texto = "O produto A custa R$23.50, o produto B custa R$19.99 e a taxa é de R$5.00.";
Pattern pattern = Pattern.compile("R\\$\\d+\\.\\d{2}");
Matcher matcher = pattern.matcher(texto);
System.out.println("Preços encontrados:");
while (matcher.find()) {
System.out.println(matcher.group());
}
}
}
R\\$\\d+\\.\\d{2}
é detalhada assim:
R\\$
: Corresponde ao literal R$
. O $
é um caractere especial em Regex e precisa ser "escapado" com \\
.\\d+
: Corresponde a uma ou mais (+
) ocorrências de um dígito (\d
).\\.
: Corresponde ao literal .
. O .
também é especial (significa "qualquer caractere") e precisa ser escapado.\\d{2}
: Corresponde a exatamente dois dígitos.
O laço while(matcher.find())
é a forma padrão de iterar sobre todas as correspondências em um texto.Tarefa 4: Censurar uma Palavra (Ignorando Maiúsculas/Minúsculas)
Descrição: Escreva um método que receba um texto e censure todas as ocorrências da palavra "senha", substituindo-a por "**". A censura deve funcionar independentemente se a palavra está em maiúsculas ou minúsculas.
Código inicial:
public class Main {
public static void main(String[] args) {
String texto = "Por favor, não digite sua Senha aqui. A senha é secreta.";
String censurado = censurarPalavra(texto);
System.out.println(censurado); // Resultado: Por favor, não digite sua ****** aqui. A ****** é secreta.
}
static String censurarPalavra(String texto) {
// Use o método replaceAll.
// Dica: para ignorar maiúsculas/minúsculas, inicie a regex com o marcador (?i).
}
}
Resposta
public class Main {
public static void main(String[] args) {
String texto = "Por favor, não digite sua Senha aqui. A senha é secreta.";
String censurado = censurarPalavra(texto);
System.out.println(censurado);
}
static String censurarPalavra(String texto) {
// (?i) é um marcador (flag) que torna a expressão que o segue case-insensitive.
return texto.replaceAll("(?i)senha", "******");
}
}
replaceAll()
pode usar marcadores (flags) dentro da própria expressão regular.
(?i)
: É um marcador que ativa o modo case-insensitive (ignora maiúsculas/minúsculas) para a expressão que o segue.senha
: É o texto a ser encontrado."******"
: É o texto que será usado na substituição.Tarefa 5: Extrair o Nome de Usuário de um Email
Descrição: Complete o método extrairUsuario
para que ele retorne apenas a parte do nome de usuário (o que vem antes do @
) de um endereço de email, usando um grupo de captura.
Código inicial:
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Main {
public static void main(String[] args) {
String email = "usuario.exemplo@provedor.com";
String usuario = extrairUsuario(email);
System.out.println(usuario); // Resultado esperado: usuario.exemplo
}
static String extrairUsuario(String email) {
// Use Pattern, Matcher e um grupo de captura () para extrair
// a parte do email antes do "@".
}
}
Resposta
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Main {
public static void main(String[] args) {
String email = "usuario.exemplo@provedor.com";
String usuario = extrairUsuario(email);
System.out.println(usuario);
}
static String extrairUsuario(String email) {
// O regex captura um ou mais caracteres de qualquer tipo (.+)
// em um grupo de captura, antes do símbolo @.
Pattern pattern = Pattern.compile("(.+)@");
Matcher matcher = pattern.matcher(email);
if (matcher.find()) {
// matcher.group(1) retorna o conteúdo capturado pelo primeiro par de parênteses.
return matcher.group(1);
}
return "Usuário não encontrado";
}
}
(.+)
: Esta é a parte principal. Os parênteses ()
criam um grupo de captura. .
significa "qualquer caractere" e +
significa "um ou mais". Juntos, capturam tudo antes do @
.@
: Corresponde ao caractere literal @
.
Quando matcher.find()
encontra uma correspondência, matcher.group()
(ou matcher.group(0)
) retornaria o texto inteiro correspondido ("usuario.exemplo@"
). No entanto, matcher.group(1)
retorna apenas o texto capturado pelo primeiro grupo, que é exatamente o que queremos.