Guide de l’API des expressions régulières Java

Guide des API d'expressions régulières Java

1. Vue d'ensemble

Dans cet article, nous discuterons de l'API Java Regex et de la manière dont les expressions régulières peuvent être utilisées dans le langage de programmation Java.

Dans le monde des expressions régulières, vous pouvez choisir parmi de nombreuses variantes, telles que grep, Perl, Python, PHP, awk et bien plus encore.

Cela signifie qu'une expression régulière qui fonctionne dans un langage de programmation peut ne pas fonctionner dans un autre. La syntaxe des expressions rationnelles en Java est très semblable à celle trouvée en Perl.

2. Installer

Pour utiliser des expressions régulières en Java, nous n’avons besoin d’aucune configuration spéciale. Le JDK contient un package spécialjava.util.regex totalement dédié aux opérations regex. Nous avons seulement besoin de l'importer dans notre code.

De plus, la classejava.lang.String a également un support intégré de regex que nous utilisons couramment dans notre code.

3. Package Java Regex

Le packagejava.util.regex se compose de trois classes:Pattern, Matcher etPatternSyntaxException:

  • L'objetPattern est une regex compilée. La classePattern ne fournit aucun constructeur public. Pour créer un modèle, nous devons d'abord invoquer l'une de ses méthodes publiques statiquescompile, qui retournera ensuite un objetPattern. Ces méthodes acceptent une expression régulière comme premier argument.

  • L'objetMatcher interprète le modèle et effectue des opérations de correspondance sur une entréeString. En outre, il ne définit aucun constructeur public. On obtient un objetMatcher en appelant la méthodematcher sur un objetPattern.

  • L'objetPatternSyntaxException est une exception non vérifiée qui indique une erreur de syntaxe dans un modèle d'expression régulière.

Nous allons explorer ces classes en détail; Cependant, nous devons d’abord comprendre comment une expression rationnelle est construite en Java.

Si vous connaissez déjà regex dans un environnement différent, vous pouvez trouver certaines différences, mais elles sont minimes.

4. Exemple simple

Let’s start with the simplest use case for a regex. Comme nous l'avons noté précédemment, lorsqu'une expression régulière est appliquée à une chaîne, elle peut correspondre à zéro ou plusieurs fois.

La forme la plus basique de correspondance de modèles prise en charge par l'APIjava.util.regex est la formematch of a String literal. Par exemple, si l'expression régulière estfoo et l'entréeString estfoo, la correspondance réussira car lesStrings sont identiques:

@Test
public void givenText_whenSimpleRegexMatches_thenCorrect() {
    Pattern pattern = Pattern.compile("foo");
    Matcher matcher = pattern.matcher("foo");

    assertTrue(matcher.find());
}

Nous créons d'abord un objetPattern en appelant sa méthode statiquecompile et en lui passant un modèle que nous voulons utiliser.

Ensuite, nous créons un objetMatcher en appelant la méthodematcher de l’objetPattern et en lui passant le texte que nous voulons vérifier les correspondances.

Après cela, nous appelons la méthodefind dans l'objet Matcher.

La méthodefind continue d'avancer dans le texte d'entrée et renvoie true pour chaque correspondance, nous pouvons donc l'utiliser pour trouver également le nombre de correspondances:

@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);
}

Puisque nous allons exécuter plus de tests, nous pouvons résumer la logique pour trouver le nombre de correspondances dans une méthode appeléerunTest:

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;
}

Lorsque nous obtenons 0 correspondances, le test devrait échouer, sinon, il devrait réussir.

5. Méta-caractères

Les méta-caractères affectent la façon dont un motif est mis en correspondance, en ajoutant une logique au motif de recherche. L'API Java prend en charge plusieurs métacaractères, le plus simple étant le point“.” qui correspond à n'importe quel caractère:

@Test
public void givenText_whenMatchesWithDotMetach_thenCorrect() {
    int matches = runTest(".", "foo");

    assertTrue(matches > 0);
}

Considérant l'exemple précédent où l'expression régulièrefoo correspondait deux fois au textefoo ainsi qu'àfoofoo. Si nous utilisions le métacaractère point dans la regex, nous n'obtiendrions pas deux correspondances dans le second cas:

@Test
public void givenRepeatedText_whenMatchesOnceWithDotMetach_thenCorrect() {
    int matches= runTest("foo.", "foofoo");

    assertEquals(matches, 1);
}

Notez le point après lesfoo dans l'expression régulière. La correspondance correspond à tous les textes précédés defoo puisque la dernière partie de point signifie tout caractère après. Ainsi, après avoir trouvé les premiersfoo, le reste est considéré comme n'importe quel caractère. C'est pourquoi il n'y a qu'une seule correspondance.

L'API prend en charge plusieurs autres méta caractères<([\{\^-=$!|]})?*+.> que nous examinerons plus en détail dans cet article.

6. Classes de personnages

En parcourant la spécification officielle de la classePattern, nous découvrirons des résumés des constructions regex prises en charge. Sous les classes de caractères, nous avons environ 6 constructions.

6.1. OU Classe

Construit en[abc]. L'un des éléments de l'ensemble est mis en correspondance:

@Test
public void givenORSet_whenMatchesAny_thenCorrect() {
    int matches = runTest("[abc]", "b");

    assertEquals(matches, 1);
}

S'ils apparaissent tous dans le texte, chacun est mis en correspondance séparément, sans égard à l'ordre:

@Test
public void givenORSet_whenMatchesAnyAndAll_thenCorrect() {
    int matches = runTest("[abc]", "cab");

    assertEquals(matches, 3);
}

Ils peuvent également être alternés dans le cadre d'unString. Dans l'exemple suivant, lorsque nous créons des mots différents en alternant la première lettre avec chaque élément de l'ensemble, ils sont tous appariés:

@Test
public void givenORSet_whenMatchesAllCombinations_thenCorrect() {
    int matches = runTest("[bcr]at", "bat cat rat");

    assertEquals(matches, 3);
}

6.2. Classe NOR

L'ensemble ci-dessus est annulé en ajoutant un caret en tant que premier élément:

@Test
public void givenNORSet_whenMatchesNon_thenCorrect() {
    int matches = runTest("[^abc]", "g");

    assertTrue(matches > 0);
}

Un autre cas:

@Test
public void givenNORSet_whenMatchesAllExceptElements_thenCorrect() {
    int matches = runTest("[^bcr]at", "sat mat eat");

    assertTrue(matches > 0);
}

6.3. Classe de gamme

Nous pouvons définir une classe qui spécifie une plage dans laquelle le texte recherché doit figurer à l'aide d'un trait d'union (-). De même, nous pouvons également annuler une plage.

Correspondance des lettres majuscules:

@Test
public void givenUpperCaseRange_whenMatchesUpperCase_
  thenCorrect() {
    int matches = runTest(
      "[A-Z]", "Two Uppercase alphabets 34 overall");

    assertEquals(matches, 2);
}

Correspondance minuscule:

@Test
public void givenLowerCaseRange_whenMatchesLowerCase_
  thenCorrect() {
    int matches = runTest(
      "[a-z]", "Two Uppercase alphabets 34 overall");

    assertEquals(matches, 26);
}

Correspondant aux lettres majuscules et minuscules:

@Test
public void givenBothLowerAndUpperCaseRange_
  whenMatchesAllLetters_thenCorrect() {
    int matches = runTest(
      "[a-zA-Z]", "Two Uppercase alphabets 34 overall");

    assertEquals(matches, 28);
}

Faire correspondre une plage de nombres donnée:

@Test
public void givenNumberRange_whenMatchesAccurately_
  thenCorrect() {
    int matches = runTest(
      "[1-5]", "Two Uppercase alphabets 34 overall");

    assertEquals(matches, 2);
}

Correspondant à une autre plage de nombres:

@Test
public void givenNumberRange_whenMatchesAccurately_
  thenCorrect2(){
    int matches = runTest(
      "[30-35]", "Two Uppercase alphabets 34 overall");

    assertEquals(matches, 1);
}

6.4. Classe Union

Une classe de caractères d'union est le résultat de la combinaison de deux ou plusieurs classes de caractères:

@Test
public void givenTwoSets_whenMatchesUnion_thenCorrect() {
    int matches = runTest("[1-3[7-9]]", "123456789");

    assertEquals(matches, 6);
}

Le test ci-dessus ne fera correspondre que 6 des 9 nombres entiers car le jeu d'unions ignore 3, 4 et 5.

6.5. Classe d'intersection

Semblable à la classe union, cette classe résulte du choix d'éléments communs entre deux ou plusieurs ensembles. Pour appliquer l'intersection, nous utilisons les&&:

@Test
public void givenTwoSets_whenMatchesIntersection_thenCorrect() {
    int matches = runTest("[1-6&&[3-9]]", "123456789");

    assertEquals(matches, 4);
}

Nous obtenons 4 correspondances car l'intersection des deux ensembles n'a que 4 éléments.

6.6. Classe de soustraction

Nous pouvons utiliser la soustraction pour annuler une ou plusieurs classes de caractères, par exemple en faisant correspondre un ensemble de nombres décimaux impairs:

@Test
public void givenSetWithSubtraction_whenMatchesAccurately_thenCorrect() {
    int matches = runTest("[0-9&&[^2468]]", "123456789");

    assertEquals(matches, 5);
}

Seuls1,3,5,7,9 seront mis en correspondance.

7. Classes de caractères prédéfinies

L'API Java regex accepte également les classes de caractères prédéfinies. Certaines des classes de caractères ci-dessus peuvent être exprimées sous une forme plus courte tout en rendant le code moins intuitif. Un aspect particulier de la version Java de cette expression régulière est le caractère d'échappement.

Comme nous le verrons, la plupart des caractères commencent par une barre oblique inverse, qui a une signification particulière en Java. Pour que ceux-ci soient compilés par la classePattern - la barre oblique inverse principale doit être échappée, c'est-à-dire \d devient\d.

Chiffres correspondants, équivalents à[0-9]:

@Test
public void givenDigits_whenMatches_thenCorrect() {
    int matches = runTest("\\d", "123");

    assertEquals(matches, 3);
}

Correspondance non-chiffres, équivalent à[^0-9]:

@Test
public void givenNonDigits_whenMatches_thenCorrect() {
    int mathces = runTest("\\D", "a6c");

    assertEquals(matches, 2);
}

Espace blanc correspondant:

@Test
public void givenWhiteSpace_whenMatches_thenCorrect() {
    int matches = runTest("\\s", "a c");

    assertEquals(matches, 1);
}

Espace non blanc correspondant:

@Test
public void givenNonWhiteSpace_whenMatches_thenCorrect() {
    int matches = runTest("\\S", "a c");

    assertEquals(matches, 2);
}

Correspondance avec un caractère de mot, équivalent à[a-zA-Z_0-9]:

@Test
public void givenWordCharacter_whenMatches_thenCorrect() {
    int matches = runTest("\\w", "hi!");

    assertEquals(matches, 2);
}

Faire correspondre un caractère autre qu'un mot:

@Test
public void givenNonWordCharacter_whenMatches_thenCorrect() {
    int matches = runTest("\\W", "hi!");

    assertEquals(matches, 1);
}

8. Quantificateurs

L'API Java regex nous permet également d'utiliser des quantificateurs. Celles-ci nous permettent d'ajuster davantage le comportement de la correspondance en spécifiant le nombre d'occurrences à comparer.

Pour faire correspondre un texte zéro ou une fois, nous utilisons le quantificateur?:

@Test
public void givenZeroOrOneQuantifier_whenMatches_thenCorrect() {
    int matches = runTest("\\a?", "hi");

    assertEquals(matches, 3);
}

Alternativement, nous pouvons utiliser la syntaxe d'accolade, également prise en charge par l'API Java regex:

@Test
public void givenZeroOrOneQuantifier_whenMatches_thenCorrect2() {
    int matches = runTest("\\a{0,1}", "hi");

    assertEquals(matches, 3);
}

Cet exemple introduit le concept de correspondance de longueur nulle. Il se trouve que si le seuil de correspondance d’un quantificateur est égal à zéro, il correspond toujours à tout dans le texte, y compris unString vide à la fin de chaque entrée. Cela signifie que même si l'entrée est vide, elle retournera une correspondance de longueur nulle.

Cela explique pourquoi nous obtenons 3 correspondances dans l'exemple ci-dessus malgré un String de longueur deux. La troisième correspondance est desString vides de longueur nulle.

Pour faire correspondre un texte à zéro ou à des temps illimités, nous nous * quantifions, il est simplement similaire à?:

@Test
public void givenZeroOrManyQuantifier_whenMatches_thenCorrect() {
     int matches = runTest("\\a*", "hi");

     assertEquals(matches, 3);
}

Alternative prise en charge:

@Test
public void givenZeroOrManyQuantifier_whenMatches_thenCorrect2() {
    int matches = runTest("\\a{0,}", "hi");

    assertEquals(matches, 3);
}

Le quantificateur avec une différence est +, il a un seuil correspondant de 1. Si leString requis ne se produit pas du tout, il n'y aura pas de correspondance, pas même unString de longueur nulle:

@Test
public void givenOneOrManyQuantifier_whenMatches_thenCorrect() {
    int matches = runTest("\\a+", "hi");

    assertFalse(matches);
}

Alternative prise en charge:

@Test
public void givenOneOrManyQuantifier_whenMatches_thenCorrect2() {
    int matches = runTest("\\a{1,}", "hi");

    assertFalse(matches);
}

Comme dans Perl et d'autres langages, la syntaxe d'accolade peut être utilisée pour faire correspondre un texte donné plusieurs fois:

@Test
public void givenBraceQuantifier_whenMatches_thenCorrect() {
    int matches = runTest("a{3}", "aaaaaa");

    assertEquals(matches, 2);
}

Dans l'exemple ci-dessus, nous obtenons deux correspondances puisqu'une correspondance ne se produit que sia apparaît trois fois de suite. Cependant, lors du test suivant, nous n'obtiendrons pas de correspondance car le texte n'apparaît que deux fois de suite:

@Test
public void givenBraceQuantifier_whenFailsToMatch_thenCorrect() {
    int matches = runTest("a{3}", "aa");

    assertFalse(matches > 0);
}

Lorsque nous utilisons une plage dans l’accolade, la correspondance sera gourmande, à partir de la partie supérieure de la plage:

@Test
public void givenBraceQuantifierWithRange_whenMatches_thenCorrect() {
    int matches = runTest("a{2,3}", "aaaa");

    assertEquals(matches, 1);
}

Nous avons spécifié au moins deux occurrences, mais pas plus de trois, donc nous obtenons une seule correspondance à la place où le correspondant voit un seulaaa eta seul a qui ne peut pas être mis en correspondance.

Cependant, l'API nous permet de spécifier une approche paresseuse ou réticente de sorte que le matcher puisse commencer à partir de l'extrémité inférieure de la plage, auquel cas correspondant à deux occurrences commeaa etaa:

@Test
public void givenBraceQuantifierWithRange_whenMatchesLazily_thenCorrect() {
    int matches = runTest("a{2,3}?", "aaaa");

    assertEquals(matches, 2);
}

9. Capturer des groupes

L'API nous permet également detreat multiple characters as a single unit through capturing groups.

Il associera des numéros aux groupes de capture et autorisera les références en arrière à l'aide de ces numéros.

Dans cette section, nous verrons quelques exemples d'utilisation de groupes de capture dans l'API regex Java.

Utilisons un groupe de capture qui correspond uniquement lorsqu'un texte d'entrée contient deux chiffres côte à côte:

@Test
public void givenCapturingGroup_whenMatches_thenCorrect() {
    int maches = runTest("(\\d\\d)", "12");

    assertEquals(matches, 1);
}

Le nombre associé à la correspondance ci-dessus est1, en utilisant une référence arrière pour indiquer au matcher que nous voulons faire correspondre une autre occurrence de la partie correspondante du texte. De cette façon, au lieu de:

@Test
public void givenCapturingGroup_whenMatches_thenCorrect2() {
    int matches = runTest("(\\d\\d)", "1212");

    assertEquals(matches, 2);
}

Lorsqu'il y a deux correspondances distinctes pour l'entrée, nous pouvons avoir une correspondance mais en propageant la même correspondance d'expression régulière pour couvrir toute la longueur de l'entrée en utilisant le référencement en arrière:

@Test
public void givenCapturingGroup_whenMatchesWithBackReference_
  thenCorrect() {
    int matches = runTest("(\\d\\d)\\1", "1212");

    assertEquals(matches, 1);
}

Où il faudrait répéter la regex sans référence en arrière pour obtenir le même résultat:

@Test
public void givenCapturingGroup_whenMatches_thenCorrect3() {
    int matches = runTest("(\\d\\d)(\\d\\d)", "1212");

    assertEquals(matches, 1);
}

De la même manière, pour tout autre nombre de répétitions, le référencement en arrière peut faire en sorte que l'appariement considère l'entrée comme une seule correspondance:

@Test
public void givenCapturingGroup_whenMatchesWithBackReference_
  thenCorrect2() {
    int matches = runTest("(\\d\\d)\\1\\1\\1", "12121212");

    assertEquals(matches, 1);
}

Mais si vous modifiez même le dernier chiffre, la correspondance échouera:

@Test
public void givenCapturingGroupAndWrongInput_
  whenMatchFailsWithBackReference_thenCorrect() {
    int matches = runTest("(\\d\\d)\\1", "1213");

    assertFalse(matches > 0);
}

Il est important de ne pas oublier les barres obliques inverses d'échappement, ce qui est crucial dans la syntaxe Java.

10. Matchers de limite

L'API Java regex prend également en charge la correspondance des limites. Si nous nous soucions de la correspondance exacte entre le texte d’entrée et le texte d’entrée, c’est ce que nous recherchons. Dans les exemples précédents, tout ce qui nous importait était de savoir si une correspondance était trouvée ou non.

Pour correspondre uniquement lorsque l'expression régulière requise est vraie au début du texte, nous utilisons le caret^.

Ce test échouera car le textedog se trouve au début:

@Test
public void givenText_whenMatchesAtBeginning_thenCorrect() {
    int matches = runTest("^dog", "dogs are friendly");

    assertTrue(matches > 0);
}

Le test suivant échouera:

@Test
public void givenTextAndWrongInput_whenMatchFailsAtBeginning_
  thenCorrect() {
    int matches = runTest("^dog", "are dogs are friendly?");

    assertFalse(matches > 0);
}

Pour correspondre uniquement lorsque l'expression régulière requise est vraie à la fin du texte, nous utilisons le caractère dollar$. Une correspondance sera trouvée dans le cas suivant:

@Test
public void givenText_whenMatchesAtEnd_thenCorrect() {
    int matches = runTest("dog$", "Man's best friend is a dog");

    assertTrue(matches > 0);
}

Et aucune correspondance ne sera trouvée ici:

@Test
public void givenTextAndWrongInput_whenMatchFailsAtEnd_thenCorrect() {
    int matches = runTest("dog$", "is a dog man's best friend?");

    assertFalse(matches > 0);
}

Si nous voulons une correspondance uniquement lorsque le texte requis est trouvé à la limite d'un mot, nous utilisons l'expression régulière\b au début et à la fin de l'expression régulière:

L'espace est une limite de mots:

@Test
public void givenText_whenMatchesAtWordBoundary_thenCorrect() {
    int matches = runTest("\\bdog\\b", "a dog is friendly");

    assertTrue(matches > 0);
}

La chaîne vide au début d'une ligne est également une limite de mot:

@Test
public void givenText_whenMatchesAtWordBoundary_thenCorrect2() {
    int matches = runTest("\\bdog\\b", "dog is man's best friend");

    assertTrue(matches > 0);
}

Ces tests réussissent parce que le début d'unString, ainsi que l'espace entre un texte et un autre, marque une limite de mot, cependant, le test suivant montre le contraire:

@Test
public void givenWrongText_whenMatchFailsAtWordBoundary_thenCorrect() {
    int matches = runTest("\\bdog\\b", "snoop dogg is a rapper");

    assertFalse(matches > 0);
}

Les caractères de deux mots apparaissant dans une ligne ne marquent pas une limite de mot, mais nous pouvons le faire passer en modifiant la fin de l'expression régulière pour rechercher une limite de non-mot:

@Test
public void givenText_whenMatchesAtWordAndNonBoundary_thenCorrect() {
    int matches = runTest("\\bdog\\B", "snoop dogg is a rapper");
    assertTrue(matches > 0);
}

11. Méthodes de classe de modèle

Auparavant, nous n'avons créé que les objetsPattern de manière basique. Cependant, cette classe a une autre variante de la méthodecompile qui accepte un ensemble d'indicateurs à côté de l'argument regex affectant la façon dont le modèle est mis en correspondance.

Ces indicateurs sont simplement des valeurs entières abstraites. Surchargeons la méthoderunTest dans la classe de test afin qu'elle puisse prendre un drapeau comme troisième argument:

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;
}

Dans cette section, nous examinerons les différents indicateurs pris en charge et leur utilisation.

Pattern.CANON_EQ

Cet indicateur permet l’équivalence canonique. Lorsque spécifié, deux caractères seront considérés comme correspondant si, et seulement si, leurs décompositions canoniques complètes correspondent.

Considérez le caractère Unicode accentuéé. Son point de code composite estu00E9. Cependant, Unicode a également un point de code distinct pour ses caractères composantse,u0065 et l'accent aigu,u0301. Dans ce cas, le caractère compositeu`00E9 ne peut pas être distingué de la séquence de deux caractèresu0065` u`0301.`

Par défaut, la correspondance ne prend pas en compte l'équivalence canonique:

@Test
public void givenRegexWithoutCanonEq_whenMatchFailsOnEquivalentUnicode_thenCorrect() {
    int matches = runTest("\u00E9", "\u0065\u0301");

    assertFalse(matches > 0);
}

Mais si nous ajoutons le drapeau, le test réussira:

@Test
public void givenRegexWithCanonEq_whenMatchesOnEquivalentUnicode_thenCorrect() {
    int matches = runTest("\u00E9", "\u0065\u0301", Pattern.CANON_EQ);

    assertTrue(matches > 0);
}

Pattern.CASE_INSENSITIVE

Cet indicateur permet la correspondance quel que soit le cas. Par défaut, la correspondance prend en compte les cas suivants:

@Test
public void givenRegexWithDefaultMatcher_whenMatchFailsOnDifferentCases_thenCorrect() {
    int matches = runTest("dog", "This is a Dog");

    assertFalse(matches > 0);
}

Donc, en utilisant cet indicateur, nous pouvons changer le comportement par défaut:

@Test
public void givenRegexWithCaseInsensitiveMatcher
  _whenMatchesOnDifferentCases_thenCorrect() {
    int matches = runTest(
      "dog", "This is a Dog", Pattern.CASE_INSENSITIVE);

    assertTrue(matches > 0);
}

Nous pouvons également utiliser l'expression d'indicateur incorporé équivalente pour obtenir le même résultat:

@Test
public void givenRegexWithEmbeddedCaseInsensitiveMatcher
  _whenMatchesOnDifferentCases_thenCorrect() {
    int matches = runTest("(?i)dog", "This is a Dog");

    assertTrue(matches > 0);
}

Pattern.COMMENTS

L'API Java permet d'inclure des commentaires à l'aide de # dans la regex. Cela peut aider à documenter des expressions rationnelles complexes qui peuvent ne pas être immédiatement évidentes pour un autre programmeur.

L'indicateur de commentaires fait en sorte que le matcher ignore tout espace blanc ou tout commentaire dans l'expression régulière et ne considère que le motif. En mode de correspondance par défaut, le test suivant échoue:

@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);
}

Cela est dû au fait que l'agent de correction recherchera l'intégralité de l'expression rationnelle dans le texte d'entrée, y compris les espaces et le caractère #. Mais lorsque nous utiliserons le drapeau, les espaces supplémentaires seront ignorés et tous les textes commençant par # seront considérés comme un commentaire à ignorer pour chaque ligne:

@Test
public void givenRegexWithComments_whenMatchesWithFlag_thenCorrect() {
    int matches = runTest(
      "dog$  #check end of text","This is a dog", Pattern.COMMENTS);

    assertTrue(matches > 0);
}

Il existe également une autre expression de drapeau intégré pour cela:

@Test
public void givenRegexWithComments_whenMatchesWithEmbeddedFlag_thenCorrect() {
    int matches = runTest(
      "(?x)dog$  #check end of text", "This is a dog");

    assertTrue(matches > 0);
}

Pattern.DOTALL

Par défaut, lorsque nous utilisons le point "." expression dans regex, nous faisons correspondre tous les caractères de l'entréeString jusqu'à ce que nous rencontrions un nouveau caractère de ligne.

En utilisant cet indicateur, le match inclura également le terminateur de ligne. Nous comprendrons mieux avec les exemples suivants. Ces exemples seront un peu différents. Puisque nous sommes intéressés à affirmer contre lesString correspondants, nous utiliserons la méthodegroup dematcher qui renvoie la correspondance précédente.

Tout d'abord, nous verrons le comportement par défaut:

@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));
}

Comme on peut le constater, seule la première partie de l'entrée, avant le terminateur de ligne, est appariée.

Désormais en modedotall, tout le texte, y compris la fin de ligne, sera mis en correspondance:

@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));
}

Nous pouvons également utiliser une expression d'indicateur intégrée pour activer le modedotall:

@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

Dans ce mode, Matcher ne donne aucune signification particulière aux métacaractères, caractères d’échappement ou syntaxe regex. Sans cet indicateur, le matcher fera correspondre l'expression rationnelle suivante à n'importe quelle entréeString:

@Test
public void givenRegex_whenMatchesWithoutLiteralFlag_thenCorrect() {
    int matches = runTest("(.*)", "text");

    assertTrue(matches > 0);
}

C'est le comportement par défaut que nous avons observé dans tous les exemples. Cependant, avec cet indicateur, aucune correspondance ne sera trouvée, car le matcher recherchera(.*) au lieu de l'interpréter:

@Test
public void givenRegex_whenMatchFailsWithLiteralFlag_thenCorrect() {
    int matches = runTest("(.*)", "text", Pattern.LITERAL);

    assertFalse(matches > 0);
}

Maintenant, si nous ajoutons la chaîne requise, le test passera:

@Test
public void givenRegex_whenMatchesWithLiteralFlag_thenCorrect() {
    int matches = runTest("(.*)", "text(.*)", Pattern.LITERAL);

    assertTrue(matches > 0);
}

Il n’existe pas de caractère de drapeau incorporé pour permettre l’analyse littérale.

Pattern.MULTILINE

Par défaut, les métacaractères^ et$ correspondent absolument au début et à la fin respectivement de la totalité de l'entréeString. Le matcher ignore les terminateurs de ligne:

@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);
}

La correspondance échoue car le matcher recherchedog à la fin de la totalité desString mais ledog est présent à la fin de la première ligne de la chaîne.

Cependant, avec le drapeau, le même test sera réussi puisque le matcher prend maintenant en compte les terminateurs de ligne. Ainsi, la chaînedog est trouvée juste avant la fin de la ligne, d'où le succès:

@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);
}

Voici la version du drapeau intégré:

@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éthodes de classe Matcher

Dans cette section, nous examinerons quelques méthodes utiles de la classeMatcher. Nous allons les regrouper par fonctionnalité pour plus de clarté.

12.1. Méthodes d'indexation

Les méthodes d'index fournissent des valeurs d'index utiles qui montrent précisément où la correspondance a été trouvée dans l'entréeString. Dans le test suivant, nous confirmerons les indices de début et de fin de la correspondance pourdog dans l'entréeString:

@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éthodes d'étude

Les méthodes d'étude passent par l'entréeString et renvoient un booléen indiquant si le motif est trouvé ou non. Les méthodesmatches etlookingAt sont couramment utilisées.

Les méthodesmatches etlookingAt tentent toutes deux de faire correspondre une séquence d'entrée à un modèle. La différence est quematches nécessite que toute la séquence d'entrée soit mise en correspondance, alors quelookingAt ne le fait pas.

Les deux méthodes commencent au début de l'entréeString:

@Test
public void whenStudyMethodsWork_thenCorrect() {
    Pattern pattern = Pattern.compile("dog");
    Matcher matcher = pattern.matcher("dogs are friendly");

    assertTrue(matcher.lookingAt());
    assertFalse(matcher.matches());
}

La méthode matches retournera true dans un cas comme celui-ci:

@Test
public void whenMatchesStudyMethodWorks_thenCorrect() {
    Pattern pattern = Pattern.compile("dog");
    Matcher matcher = pattern.matcher("dog");

    assertTrue(matcher.matches());
}

12.3. Méthodes de remplacement

Les méthodes de remplacement sont utiles pour remplacer du texte dans une chaîne d'entrée. Les plus courants sontreplaceFirst etreplaceAll.

Les méthodesreplaceFirst etreplaceAll remplacent le texte qui correspond à une expression régulière donnée. Comme leur nom l'indique,replaceFirst remplace la première occurrence etreplaceAll remplace toutes les occurrences:

@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);
}

Remplacer toutes les occurrences:

@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. Conclusion

Dans cet article, nous avons appris à utiliser les expressions régulières en Java et avons également exploré les fonctionnalités les plus importantes du packagejava.util.regex.

Le code source complet du projet, y compris tous les exemples de code utilisés ici, se trouve dans lesGitHub project.