Um guia para API de expressões regulares Java
1. Visão geral
Neste artigo, discutiremos a API Java Regex e como expressões regulares podem ser usadas na linguagem de programação Java.
No mundo das expressões regulares, há muitos tipos diferentes para escolher, como grep, Perl, Python, PHP, awk e muito mais.
Isso significa que uma expressão regular que funciona em uma linguagem de programação pode não funcionar em outra. A sintaxe da expressão regular no Java é mais semelhante à encontrada no Perl.
2. Configuração
Para usar expressões regulares em Java, não precisamos de nenhuma configuração especial. O JDK contém um pacote especialjava.util.regex totalmente dedicado a operações regex. Nós só precisamos importá-lo para o nosso código.
Além disso, a classejava.lang.String também possui suporte regex embutido que normalmente usamos em nosso código.
3. Pacote Java Regex
O pacotejava.util.regex consiste em três classes:Pattern, MatcherePatternSyntaxException:
-
O objetoPattern é um regex compilado. A classePattern não fornece construtores públicos. Para criar um padrão, devemos primeiro invocar um de seus métodos públicos estáticoscompile, que retornará um objetoPattern. Esses métodos aceitam uma expressão regular como o primeiro argumento.
-
O objetoMatcher interpreta o padrão e executa operações de correspondência em uma entradaString. Também não define construtores públicos. Obtemos um objetoMatcher invocando o métodomatcher em um objetoPattern.
-
O objetoPatternSyntaxException é uma exceção não verificada que indica um erro de sintaxe em um padrão de expressão regular.
Vamos explorar essas classes em detalhes; no entanto, precisamos primeiro entender como uma regex é construída em Java.
Se você já conhece o regex de um ambiente diferente, pode encontrar certas diferenças, mas elas são mínimas.
4. Exemplo Simples
Let’s start with the simplest use case for a regex. Como observamos anteriormente, quando uma regex é aplicada a uma String, ela pode corresponder a zero ou mais vezes.
A forma mais básica de correspondência de padrões suportada pela APIjava.util.regex ématch of a String literal. Por exemplo, se a expressão regular forfooe a entradaString forfoo, a correspondência será bem-sucedida porque osStrings são idênticos:
@Test
public void givenText_whenSimpleRegexMatches_thenCorrect() {
Pattern pattern = Pattern.compile("foo");
Matcher matcher = pattern.matcher("foo");
assertTrue(matcher.find());
}
Primeiro criamos um objetoPattern chamando seu métodocompile estático e passando um padrão que queremos usar.
Em seguida, criamos um objetoMatcher chamando o métodomatcher do objetoPattern e passando o texto que queremos verificar se há correspondências.
Depois disso, chamamos o métodofind no objeto Matcher.
O métodofind continua avançando pelo texto de entrada e retorna verdadeiro para todas as correspondências, portanto, podemos usá-lo para encontrar a contagem de correspondências também:
@Test
public void givenText_whenSimpleRegexMatchesTwice_thenCorrect() {
Pattern pattern = Pattern.compile("foo");
Matcher matcher = pattern.matcher("foofoo");
int matches = 0;
while (matcher.find()) {
matches++;
}
assertEquals(matches, 2);
}
Como executaremos mais testes, podemos abstrair a lógica para encontrar o número de correspondências em um método chamadorunTest:
public static int runTest(String regex, String text) {
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(text);
int matches = 0;
while (matcher.find()) {
matches++;
}
return matches;
}
Quando obtivermos 0 correspondências, o teste deverá falhar, caso contrário, deverá passar.
5. Metacaracteres
Meta caracteres afetam a maneira como um padrão é correspondido, de forma a adicionar lógica ao padrão de pesquisa. A API Java oferece suporte a vários metacaracteres, sendo o mais simples o ponto“.” que corresponde a qualquer caractere:
@Test
public void givenText_whenMatchesWithDotMetach_thenCorrect() {
int matches = runTest(".", "foo");
assertTrue(matches > 0);
}
Considerando o exemplo anterior, onde regexfoo correspondeu ao textofoo, bem comofoofoo, duas vezes. Se usássemos o metacaractere de ponto na regex, não obteríamos duas correspondências no segundo caso:
@Test
public void givenRepeatedText_whenMatchesOnceWithDotMetach_thenCorrect() {
int matches= runTest("foo.", "foofoo");
assertEquals(matches, 1);
}
Observe o ponto após ofoo na regex. O matcher corresponde a cada texto que é precedido porfoo desde a última parte do ponto significa qualquer caractere depois. Portanto, após encontrar os primeirosfoo, o resto é visto como qualquer caractere. É por isso que há apenas uma partida.
A API suporta vários outros metacaracteres<([\{\^-=$!|]})?*+.>, os quais examinaremos mais a fundo neste artigo.
6. Classes de caracteres
Navegando pela especificação oficial de classePattern, descobriremos resumos de construções regex suportadas. Nas classes de caracteres, temos cerca de 6 construções.
6.1. Classe OR
Construído como[abc]. Qualquer um dos elementos no conjunto é correspondido:
@Test
public void givenORSet_whenMatchesAny_thenCorrect() {
int matches = runTest("[abc]", "b");
assertEquals(matches, 1);
}
Se todos aparecerem no texto, cada um deles corresponderá separadamente, sem levar em conta a ordem:
@Test
public void givenORSet_whenMatchesAnyAndAll_thenCorrect() {
int matches = runTest("[abc]", "cab");
assertEquals(matches, 3);
}
Eles também podem ser alternados como parte de umString. No exemplo a seguir, quando criamos palavras diferentes alternando a primeira letra com cada elemento do conjunto, todas são correspondentes:
@Test
public void givenORSet_whenMatchesAllCombinations_thenCorrect() {
int matches = runTest("[bcr]at", "bat cat rat");
assertEquals(matches, 3);
}
6.2. Classe NOR
O conjunto acima é negado adicionando um sinal de intercalação como o primeiro elemento:
@Test
public void givenNORSet_whenMatchesNon_thenCorrect() {
int matches = runTest("[^abc]", "g");
assertTrue(matches > 0);
}
Outro caso:
@Test
public void givenNORSet_whenMatchesAllExceptElements_thenCorrect() {
int matches = runTest("[^bcr]at", "sat mat eat");
assertTrue(matches > 0);
}
6.3. Classe de alcance
Podemos definir uma classe que especifica um intervalo dentro do qual o texto correspondente deve cair usando um hífen (-). Da mesma forma, também podemos negar um intervalo.
Letras maiúsculas correspondentes:
@Test
public void givenUpperCaseRange_whenMatchesUpperCase_
thenCorrect() {
int matches = runTest(
"[A-Z]", "Two Uppercase alphabets 34 overall");
assertEquals(matches, 2);
}
Letras minúsculas correspondentes:
@Test
public void givenLowerCaseRange_whenMatchesLowerCase_
thenCorrect() {
int matches = runTest(
"[a-z]", "Two Uppercase alphabets 34 overall");
assertEquals(matches, 26);
}
Correspondência de letras maiúsculas e minúsculas:
@Test
public void givenBothLowerAndUpperCaseRange_
whenMatchesAllLetters_thenCorrect() {
int matches = runTest(
"[a-zA-Z]", "Two Uppercase alphabets 34 overall");
assertEquals(matches, 28);
}
Correspondendo a um determinado intervalo de números:
@Test
public void givenNumberRange_whenMatchesAccurately_
thenCorrect() {
int matches = runTest(
"[1-5]", "Two Uppercase alphabets 34 overall");
assertEquals(matches, 2);
}
Correspondendo a outro intervalo de números:
@Test
public void givenNumberRange_whenMatchesAccurately_
thenCorrect2(){
int matches = runTest(
"[30-35]", "Two Uppercase alphabets 34 overall");
assertEquals(matches, 1);
}
6.4. Classe União
Uma classe de caractere de união é resultado da combinação de duas ou mais classes de caracteres:
@Test
public void givenTwoSets_whenMatchesUnion_thenCorrect() {
int matches = runTest("[1-3[7-9]]", "123456789");
assertEquals(matches, 6);
}
O teste acima corresponderá apenas a 6 dos 9 números inteiros porque o conjunto de união ignora 3, 4 e 5.
6.5. Classe de interseção
Semelhante à classe union, essa classe resulta da seleção de elementos comuns entre dois ou mais conjuntos. Para aplicar a interseção, usamos o&&:
@Test
public void givenTwoSets_whenMatchesIntersection_thenCorrect() {
int matches = runTest("[1-6&&[3-9]]", "123456789");
assertEquals(matches, 4);
}
Temos 4 partidas porque a interseção dos dois conjuntos tem apenas 4 elementos.
6.6. Classe de Subtração
Podemos usar a subtração para negar uma ou mais classes de caracteres, por exemplo, correspondendo a um conjunto de números decimais ímpares:
@Test
public void givenSetWithSubtraction_whenMatchesAccurately_thenCorrect() {
int matches = runTest("[0-9&&[^2468]]", "123456789");
assertEquals(matches, 5);
}
Apenas1,3,5,7,9 serão correspondidos.
7. Classes de personagem predefinidas
A API Java regex também aceita classes de caracteres predefinidas. Algumas das classes de caracteres acima podem ser expressas em forma mais curta, embora tornando o código menos intuitivo. Um aspecto especial da versão Java desse regex é o caractere de escape.
Como veremos, a maioria dos caracteres começará com uma barra invertida, que tem um significado especial em Java. Para que sejam compilados pela classePattern - a barra invertida inicial deve ter escape, ou seja, \d torna-se\d.
Dígitos correspondentes, equivalentes a[0-9]:
@Test
public void givenDigits_whenMatches_thenCorrect() {
int matches = runTest("\\d", "123");
assertEquals(matches, 3);
}
Correspondência de não dígitos, equivalente a[^0-9]:
@Test
public void givenNonDigits_whenMatches_thenCorrect() {
int mathces = runTest("\\D", "a6c");
assertEquals(matches, 2);
}
Espaço em branco correspondente:
@Test
public void givenWhiteSpace_whenMatches_thenCorrect() {
int matches = runTest("\\s", "a c");
assertEquals(matches, 1);
}
Correspondência de espaço não branco:
@Test
public void givenNonWhiteSpace_whenMatches_thenCorrect() {
int matches = runTest("\\S", "a c");
assertEquals(matches, 2);
}
Correspondência de um caractere de palavra, equivalente a[a-zA-Z_0-9]:
@Test
public void givenWordCharacter_whenMatches_thenCorrect() {
int matches = runTest("\\w", "hi!");
assertEquals(matches, 2);
}
Correspondendo a um caractere que não é uma palavra:
@Test
public void givenNonWordCharacter_whenMatches_thenCorrect() {
int matches = runTest("\\W", "hi!");
assertEquals(matches, 1);
}
8. Quantificadores
A API regex Java também nos permite usar quantificadores. Isso nos permite ajustar ainda mais o comportamento da correspondência, especificando o número de ocorrências a serem comparadas.
Para corresponder a um texto zero ou uma vez, usamos o quantificador?:
@Test
public void givenZeroOrOneQuantifier_whenMatches_thenCorrect() {
int matches = runTest("\\a?", "hi");
assertEquals(matches, 3);
}
Como alternativa, podemos usar a sintaxe brace, também suportada pela API Java regex:
@Test
public void givenZeroOrOneQuantifier_whenMatches_thenCorrect2() {
int matches = runTest("\\a{0,1}", "hi");
assertEquals(matches, 3);
}
Este exemplo apresenta o conceito de correspondências de comprimento zero. Acontece que se o limite de um quantificador para correspondência é zero, ele sempre corresponde a tudo no texto, incluindo umString vazio no final de cada entrada. Isso significa que, mesmo que a entrada esteja vazia, ela retornará uma correspondência de comprimento zero.
Isso explica porque obtivemos 3 correspondências no exemplo acima, apesar de termos String de comprimento dois. A terceira correspondência éString vazia de comprimento zero.
Para corresponder a um texto zero ou vezes sem limites, nós nos * quantificador, é semelhante a?:
@Test
public void givenZeroOrManyQuantifier_whenMatches_thenCorrect() {
int matches = runTest("\\a*", "hi");
assertEquals(matches, 3);
}
Alternativa suportada:
@Test
public void givenZeroOrManyQuantifier_whenMatches_thenCorrect2() {
int matches = runTest("\\a{0,}", "hi");
assertEquals(matches, 3);
}
O quantificador com uma diferença é +, ele tem um limite correspondente de 1. Se oString necessário não ocorrer, não haverá correspondência, nem mesmo umString de comprimento zero:
@Test
public void givenOneOrManyQuantifier_whenMatches_thenCorrect() {
int matches = runTest("\\a+", "hi");
assertFalse(matches);
}
Alternativa suportada:
@Test
public void givenOneOrManyQuantifier_whenMatches_thenCorrect2() {
int matches = runTest("\\a{1,}", "hi");
assertFalse(matches);
}
Como está no Perl e em outros idiomas, a sintaxe do colchete pode ser usada para corresponder a um determinado texto várias vezes:
@Test
public void givenBraceQuantifier_whenMatches_thenCorrect() {
int matches = runTest("a{3}", "aaaaaa");
assertEquals(matches, 2);
}
No exemplo acima, obtemos duas correspondências, pois uma correspondência ocorre apenas sea aparecer três vezes consecutivas. No entanto, no próximo teste não obteremos uma correspondência, pois o texto aparece apenas duas vezes consecutivas:
@Test
public void givenBraceQuantifier_whenFailsToMatch_thenCorrect() {
int matches = runTest("a{3}", "aa");
assertFalse(matches > 0);
}
Quando usamos um intervalo entre chaves, a correspondência será gananciosa, correspondendo a partir da extremidade superior do intervalo:
@Test
public void givenBraceQuantifierWithRange_whenMatches_thenCorrect() {
int matches = runTest("a{2,3}", "aaaa");
assertEquals(matches, 1);
}
Especificamos pelo menos duas ocorrências, mas não excedendo três, então obtemos uma única correspondência, em que o combinador vê um únicoaaaea único a que não pode ser correspondido.
No entanto, a API nos permite especificar uma abordagem preguiçosa ou relutante, de modo que o combinador possa começar da extremidade inferior do intervalo, caso em que combinando duas ocorrências comoaaeaa:
@Test
public void givenBraceQuantifierWithRange_whenMatchesLazily_thenCorrect() {
int matches = runTest("a{2,3}?", "aaaa");
assertEquals(matches, 2);
}
9. Capturando grupos
A API também nos permitetreat multiple characters as a single unit through capturing groups.
Ele anexará os números aos grupos de captura e permitirá a referência posterior usando esses números.
Nesta seção, veremos alguns exemplos de como usar grupos de captura na API Java regex.
Vamos usar um grupo de captura que corresponde apenas quando um texto de entrada contém dois dígitos próximos um do outro:
@Test
public void givenCapturingGroup_whenMatches_thenCorrect() {
int maches = runTest("(\\d\\d)", "12");
assertEquals(matches, 1);
}
O número anexado à correspondência acima é1, usando uma referência anterior para dizer ao comparador que queremos corresponder a outra ocorrência da parte correspondida do texto. Dessa forma, em vez de:
@Test
public void givenCapturingGroup_whenMatches_thenCorrect2() {
int matches = runTest("(\\d\\d)", "1212");
assertEquals(matches, 2);
}
Onde há duas correspondências separadas para a entrada, podemos ter uma correspondência, mas propagando a mesma correspondência de regex para abranger todo o comprimento da entrada usando a referência remota:
@Test
public void givenCapturingGroup_whenMatchesWithBackReference_
thenCorrect() {
int matches = runTest("(\\d\\d)\\1", "1212");
assertEquals(matches, 1);
}
Onde teríamos que repetir a regex sem voltar a fazer referência para obter o mesmo resultado:
@Test
public void givenCapturingGroup_whenMatches_thenCorrect3() {
int matches = runTest("(\\d\\d)(\\d\\d)", "1212");
assertEquals(matches, 1);
}
Da mesma forma, para qualquer outro número de repetições, a referência posterior pode fazer com que o correspondente veja a entrada como uma única correspondência:
@Test
public void givenCapturingGroup_whenMatchesWithBackReference_
thenCorrect2() {
int matches = runTest("(\\d\\d)\\1\\1\\1", "12121212");
assertEquals(matches, 1);
}
Mas se você alterar até o último dígito, a correspondência falhará:
@Test
public void givenCapturingGroupAndWrongInput_
whenMatchFailsWithBackReference_thenCorrect() {
int matches = runTest("(\\d\\d)\\1", "1213");
assertFalse(matches > 0);
}
É importante não esquecer as barras invertidas de escape, isso é crucial na sintaxe do Java.
10. Boundary Matchers
A API regex Java também suporta correspondência de limites. Se nos preocupamos com exatamente onde exatamente no texto de entrada a correspondência deve ocorrer, é isso que estamos procurando. Com os exemplos anteriores, nos preocupávamos apenas com encontrar ou não uma correspondência.
Para corresponder apenas quando a regex necessária é verdadeira no início do texto, usamos o acento circunflexo^.
Este teste falhará porque o textodog pode ser encontrado no início:
@Test
public void givenText_whenMatchesAtBeginning_thenCorrect() {
int matches = runTest("^dog", "dogs are friendly");
assertTrue(matches > 0);
}
O seguinte teste falhará:
@Test
public void givenTextAndWrongInput_whenMatchFailsAtBeginning_
thenCorrect() {
int matches = runTest("^dog", "are dogs are friendly?");
assertFalse(matches > 0);
}
Para corresponder apenas quando a regex exigida for verdadeira no final do texto, usamos o caractere de dólar$.. Uma correspondência será encontrada no seguinte caso:
@Test
public void givenText_whenMatchesAtEnd_thenCorrect() {
int matches = runTest("dog$", "Man's best friend is a dog");
assertTrue(matches > 0);
}
E nenhuma correspondência será encontrada aqui:
@Test
public void givenTextAndWrongInput_whenMatchFailsAtEnd_thenCorrect() {
int matches = runTest("dog$", "is a dog man's best friend?");
assertFalse(matches > 0);
}
Se quisermos uma correspondência apenas quando o texto necessário for encontrado em um limite de palavra, usamos\b regex no início e no final do regex:
Espaço é um limite de palavras:
@Test
public void givenText_whenMatchesAtWordBoundary_thenCorrect() {
int matches = runTest("\\bdog\\b", "a dog is friendly");
assertTrue(matches > 0);
}
A cadeia vazia no início de uma linha também é um limite de palavras:
@Test
public void givenText_whenMatchesAtWordBoundary_thenCorrect2() {
int matches = runTest("\\bdog\\b", "dog is man's best friend");
assertTrue(matches > 0);
}
Esses testes são aprovados porque o início de umString, bem como o espaço entre um texto e outro, marca um limite de palavra, no entanto, o teste a seguir mostra o oposto:
@Test
public void givenWrongText_whenMatchFailsAtWordBoundary_thenCorrect() {
int matches = runTest("\\bdog\\b", "snoop dogg is a rapper");
assertFalse(matches > 0);
}
Os caracteres de duas palavras que aparecem em uma linha não marcam o limite de uma palavra, mas podemos fazê-lo passar alterando o final da regex para procurar um limite de não palavra:
@Test
public void givenText_whenMatchesAtWordAndNonBoundary_thenCorrect() {
int matches = runTest("\\bdog\\B", "snoop dogg is a rapper");
assertTrue(matches > 0);
}
11. Métodos de classe de padrão
Anteriormente, criamos apenas objetosPattern de uma maneira básica. No entanto, essa classe tem outra variante do métodocompile que aceita um conjunto de sinalizadores junto com o argumento regex que afeta a maneira como o padrão é correspondido.
Esses sinalizadores são simplesmente valores inteiros abstraídos. Vamos sobrecarregar o métodorunTest na classe de teste para que ele possa receber um sinalizador como o terceiro argumento:
public static int runTest(String regex, String text, int flags) {
pattern = Pattern.compile(regex, flags);
matcher = pattern.matcher(text);
int matches = 0;
while (matcher.find()){
matches++;
}
return matches;
}
Nesta seção, veremos os diferentes sinalizadores suportados e como eles são usados.
Pattern.CANON_EQ
Este sinalizador permite equivalência canônica. Quando especificado, dois caracteres serão considerados como correspondentes se, e somente se, suas decomposições canônicas completas corresponderem.
Considere o caractere Unicode acentuadoé. Seu ponto de código composto éu00E9. No entanto, o Unicode também tem um ponto de código separado para seus caracteres componentese,u0065e o acento agudo,u0301. Neste caso, o caractere compostou`00E9 é indistinguível da sequência de dois caracteresu0065` u`0301.
Por padrão, a correspondência não leva em consideração a equivalência canônica:
@Test
public void givenRegexWithoutCanonEq_whenMatchFailsOnEquivalentUnicode_thenCorrect() {
int matches = runTest("\u00E9", "\u0065\u0301");
assertFalse(matches > 0);
}
Mas se adicionarmos o sinalizador, o teste será aprovado:
@Test
public void givenRegexWithCanonEq_whenMatchesOnEquivalentUnicode_thenCorrect() {
int matches = runTest("\u00E9", "\u0065\u0301", Pattern.CANON_EQ);
assertTrue(matches > 0);
}
Pattern.CASE_INSENSITIVE
Este sinalizador permite a correspondência independentemente do caso. Por padrão, a correspondência leva em consideração o caso:
@Test
public void givenRegexWithDefaultMatcher_whenMatchFailsOnDifferentCases_thenCorrect() {
int matches = runTest("dog", "This is a Dog");
assertFalse(matches > 0);
}
Portanto, usando esse sinalizador, podemos alterar o comportamento padrão:
@Test
public void givenRegexWithCaseInsensitiveMatcher
_whenMatchesOnDifferentCases_thenCorrect() {
int matches = runTest(
"dog", "This is a Dog", Pattern.CASE_INSENSITIVE);
assertTrue(matches > 0);
}
Também podemos usar a expressão de bandeira incorporada equivalente para obter o mesmo resultado:
@Test
public void givenRegexWithEmbeddedCaseInsensitiveMatcher
_whenMatchesOnDifferentCases_thenCorrect() {
int matches = runTest("(?i)dog", "This is a Dog");
assertTrue(matches > 0);
}
Pattern.COMMENTS
A API Java permite incluir comentários usando # no regex. Isso pode ajudar na documentação de expressões regulares complexas que podem não ser imediatamente óbvias para outro programador.
O sinalizador de comentários faz com que o correspondente ignore qualquer espaço em branco ou comentários na regex e considere apenas o padrão. No modo de correspondência padrão, o seguinte teste falharia:
@Test
public void givenRegexWithComments_whenMatchFailsWithoutFlag_thenCorrect() {
int matches = runTest(
"dog$ #check for word dog at end of text", "This is a dog");
assertFalse(matches > 0);
}
Isso ocorre porque o correspondente procurará toda a expressão regular no texto de entrada, incluindo os espaços e o caractere #. Mas quando usamos o sinalizador, ele ignora os espaços extras e todos os textos que começam com # serão vistos como um comentário a ser ignorado para cada linha:
@Test
public void givenRegexWithComments_whenMatchesWithFlag_thenCorrect() {
int matches = runTest(
"dog$ #check end of text","This is a dog", Pattern.COMMENTS);
assertTrue(matches > 0);
}
Há também uma expressão alternativa de sinalizador incorporado para isso:
@Test
public void givenRegexWithComments_whenMatchesWithEmbeddedFlag_thenCorrect() {
int matches = runTest(
"(?x)dog$ #check end of text", "This is a dog");
assertTrue(matches > 0);
}
Pattern.DOTALL
Por padrão, quando usamos o ponto “.” expressão em regex, estamos combinando todos os caracteres na entradaString até encontrarmos um novo caractere de linha.
Usando esse sinalizador, a correspondência também incluirá o terminador de linha. Vamos entender melhor com os seguintes exemplos. Esses exemplos serão um pouco diferentes. Como estamos interessados em comparar osString correspondidos, usaremos o métodomatcher'sgroup que retorna a correspondência anterior.
Primeiro, veremos o comportamento padrão:
@Test
public void givenRegexWithLineTerminator_whenMatchFails_thenCorrect() {
Pattern pattern = Pattern.compile("(.*)");
Matcher matcher = pattern.matcher(
"this is a text" + System.getProperty("line.separator")
+ " continued on another line");
matcher.find();
assertEquals("this is a text", matcher.group(1));
}
Como podemos ver, apenas a primeira parte da entrada antes da correspondência do terminador de linha.
Agora, no mododotall, todo o texto incluindo o terminador de linha será correspondido:
@Test
public void givenRegexWithLineTerminator_whenMatchesWithDotall_thenCorrect() {
Pattern pattern = Pattern.compile("(.*)", Pattern.DOTALL);
Matcher matcher = pattern.matcher(
"this is a text" + System.getProperty("line.separator")
+ " continued on another line");
matcher.find();
assertEquals(
"this is a text" + System.getProperty("line.separator")
+ " continued on another line", matcher.group(1));
}
Também podemos usar uma expressão de sinalização incorporada para ativar o mododotall:
@Test
public void givenRegexWithLineTerminator_whenMatchesWithEmbeddedDotall
_thenCorrect() {
Pattern pattern = Pattern.compile("(?s)(.*)");
Matcher matcher = pattern.matcher(
"this is a text" + System.getProperty("line.separator")
+ " continued on another line");
matcher.find();
assertEquals(
"this is a text" + System.getProperty("line.separator")
+ " continued on another line", matcher.group(1));
}
Pattern.LITERAL
Quando neste modo, o matcher não dá significado especial a nenhum metacaractere, caractere de escape ou sintaxe regex. Sem este sinalizador, o matcher corresponderá ao seguinte regex contra qualquer entradaString:
@Test
public void givenRegex_whenMatchesWithoutLiteralFlag_thenCorrect() {
int matches = runTest("(.*)", "text");
assertTrue(matches > 0);
}
Esse é o comportamento padrão que temos visto em todos os exemplos. No entanto, com este sinalizador, nenhuma correspondência será encontrada, pois o matcher estará procurando por(.*) em vez de interpretá-lo:
@Test
public void givenRegex_whenMatchFailsWithLiteralFlag_thenCorrect() {
int matches = runTest("(.*)", "text", Pattern.LITERAL);
assertFalse(matches > 0);
}
Agora, se adicionarmos a string necessária, o teste será aprovado:
@Test
public void givenRegex_whenMatchesWithLiteralFlag_thenCorrect() {
int matches = runTest("(.*)", "text(.*)", Pattern.LITERAL);
assertTrue(matches > 0);
}
Não há caractere de sinalizador incorporado para ativar a análise literal.
Pattern.MULTILINE
Por padrão, os metacaracteres de^e$ correspondem absolutamente no início e no final, respectivamente, de toda a entradaString. O correspondente desconsidera qualquer terminador de linha:
@Test
public void givenRegex_whenMatchFailsWithoutMultilineFlag_thenCorrect() {
int matches = runTest(
"dog$", "This is a dog" + System.getProperty("line.separator")
+ "this is a fox");
assertFalse(matches > 0);
}
A correspondência falha porque o combinador procuradog no final de todo oString, masdog está presente no final da primeira linha da string.
No entanto, com a bandeira, o mesmo teste será aprovado, já que o competidor agora leva em consideração os terminadores de linha. Portanto, a Stringdog é encontrada pouco antes de a linha terminar, portanto, sucesso:
@Test
public void givenRegex_whenMatchesWithMultilineFlag_thenCorrect() {
int matches = runTest(
"dog$", "This is a dog" + System.getProperty("line.separator")
+ "this is a fox", Pattern.MULTILINE);
assertTrue(matches > 0);
}
Aqui está a versão do sinalizador incorporado:
@Test
public void givenRegex_whenMatchesWithEmbeddedMultilineFlag_
thenCorrect() {
int matches = runTest(
"(?m)dog$", "This is a dog" + System.getProperty("line.separator")
+ "this is a fox");
assertTrue(matches > 0);
}
12. Métodos de classe Matcher
Nesta seção, veremos alguns métodos úteis da classeMatcher. Vamos agrupá-los de acordo com a funcionalidade para maior clareza.
12.1. Métodos de índice
Os métodos de índice fornecem valores de índice úteis que mostram precisamente onde a correspondência foi encontrada na entradaString. No teste a seguir, confirmaremos os índices de início e fim da correspondência paradog na entradaString:
@Test
public void givenMatch_whenGetsIndices_thenCorrect() {
Pattern pattern = Pattern.compile("dog");
Matcher matcher = pattern.matcher("This dog is mine");
matcher.find();
assertEquals(5, matcher.start());
assertEquals(8, matcher.end());
}
12.2. Métodos de Estudo
Os métodos de estudo passam pela entradaStringe retornam um booleano indicando se o padrão foi encontrado ou não. Normalmente usados são os métodosmatcheselookingAt.
Os métodosmatcheselookingAt tentam combinar uma sequência de entrada com um padrão. A diferença é quematches requer que toda a sequência de entrada seja correspondida, enquantolookingAt não.
Ambos os métodos começam no início da entradaString:
@Test
public void whenStudyMethodsWork_thenCorrect() {
Pattern pattern = Pattern.compile("dog");
Matcher matcher = pattern.matcher("dogs are friendly");
assertTrue(matcher.lookingAt());
assertFalse(matcher.matches());
}
O método match retornará true em um caso como este:
@Test
public void whenMatchesStudyMethodWorks_thenCorrect() {
Pattern pattern = Pattern.compile("dog");
Matcher matcher = pattern.matcher("dog");
assertTrue(matcher.matches());
}
12.3. Métodos de substituição
Os métodos de substituição são úteis para substituir o texto em uma sequência de entrada. Os mais comuns sãoreplaceFirstereplaceAll.
Os métodosreplaceFirstereplaceAll substituem o texto que corresponde a uma determinada expressão regular. Como seus nomes indicam,replaceFirst substitui a primeira ocorrência ereplaceAll substitui todas as ocorrências:
@Test
public void whenReplaceFirstWorks_thenCorrect() {
Pattern pattern = Pattern.compile("dog");
Matcher matcher = pattern.matcher(
"dogs are domestic animals, dogs are friendly");
String newStr = matcher.replaceFirst("cat");
assertEquals(
"cats are domestic animals, dogs are friendly", newStr);
}
Substitua todas as ocorrências:
@Test
public void whenReplaceAllWorks_thenCorrect() {
Pattern pattern = Pattern.compile("dog");
Matcher matcher = pattern.matcher(
"dogs are domestic animals, dogs are friendly");
String newStr = matcher.replaceAll("cat");
assertEquals("cats are domestic animals, cats are friendly", newStr);
}
13. Conclusão
Neste artigo, aprendemos como usar expressões regulares em Java e também exploramos os recursos mais importantes do pacotejava.util.regex.
O código-fonte completo do projeto, incluindo todos os exemplos de código usados aqui, pode ser encontrado emGitHub project.