Classes Pattern et Matcher en Java
Compilez des expressions régulières et trouvez des correspondances en Java avec les classes Pattern et Matcher.
Les expressions régulières en Java se trouvent dans le package java.util.regex, et presque tout ce package se résume à deux classes : Pattern et Matcher. Un Pattern est une expression régulière compilée — la règle. Un Matcher est le moteur qui applique cette règle sur une entrée et rapporte ce qu'il a trouvé. Cette séparation vous permet de compiler une expression une seule fois et de la réutiliser sur des milliers d'entrées, ce qui est la différence entre du code regex rapide et du code regex lent.
Ce chapitre couvre la compilation d'un Pattern, l'utilisation d'un Matcher, la distinction cruciale entre find() et matches(), les groupes capturants et nommés, et les drapeaux. Si vous débutez avec la syntaxe elle-même, commencez par les chapitres introduction aux regex et syntaxe des regex.
Pattern : compiler une fois, réutiliser indéfiniment
Pattern.compile(String regex) analyse la syntaxe de l'expression régulière et retourne un Pattern immuable et thread-safe. La compilation est l'étape coûteuse, alors faites-la une seule fois — généralement dans un champ static final — et partagez le résultat. Les méthodes pratiques de String (matches, replaceAll, split) recompilent le pattern à chaque appel, ce qui convient pour une utilisation ponctuelle mais est inutilement coûteux dans une boucle.
// Good: compile once, reuse
private static final Pattern EMAIL =
Pattern.compile("[\\w.+-]+@[\\w.-]+\\.[a-z]{2,}");
// Wasteful in a loop: String.matches recompiles every iteration
for (String s : lines) {
if (s.matches("[\\w.+-]+@[\\w.-]+\\.[a-z]{2,}")) { /* ... */ }
}Notez les doubles barres obliques inverses : \\w dans le code source Java correspond au token regex \w, car la barre oblique inverse doit d'abord survivre à l'échappement propre à Java.
Matcher : le moteur avec état
Un Pattern ne contient ni entrée ni position — il est simplement la règle. Appeler pattern.matcher(input) produit un Matcher lié à cette entrée, et le Matcher porte tout l'état mutable : la position de recherche courante, les limites de la dernière correspondance et les groupes capturés. Étant doté d'un état, un Matcher n'est pas thread-safe ; donnez à chaque thread le sien.
| Méthode | Ce qu'elle fait |
|---|---|
matches() | Teste si l'entrée entière correspond au pattern |
lookingAt() | Teste si l'entrée correspond en commençant au début (sans forcément aller jusqu'à la fin) |
find() | Trouve la prochaine correspondance n'importe où dans l'entrée ; retourne true et avance |
group() / group(n) | Retourne la correspondance entière, ou le groupe capturant n |
start() / end() | Index du premier caractère de la correspondance, et un après le dernier |
replaceAll(repl) | Remplace chaque correspondance, avec $1, $2 comme références arrière aux groupes |
reset() | Réinitialise le matcher à la position zéro (optionnellement avec une nouvelle entrée) |
find() versus matches() : la confusion la plus fréquente
matches() est ancré à toute la chaîne — il retourne true uniquement si le pattern consomme l'entrée entière. find() est un scanner : il cherche le pattern n'importe où et peut être appelé de manière répétée pour parcourir chaque occurrence.
Pattern p = Pattern.compile("\\d+");
System.out.println(p.matcher("abc123").matches()); // false — whole string isn't digits
System.out.println(p.matcher("abc123").find()); // true — found "123" inside
Matcher m = p.matcher("a1 b22 c333");
while (m.find()) {
System.out.println(m.group() + " @ " + m.start()); // 1@1, 22@4, 333@8
}Un bug fréquent est d'appeler group() avant un matches()/find() réussi — cela lève une IllegalStateException, car il n'y a pas encore de correspondance à lire.
Groupes capturants, groupes nommés et remplacement
Les parenthèses dans une expression régulière créent des groupes capturants, numérotés de gauche à droite en commençant par 1 (le groupe 0 est la correspondance entière). Java prend également en charge les groupes nommés avec (?<name>...), que vous récupérez via group("name") — beaucoup plus lisible que de compter des parenthèses. Dans les chaînes de remplacement, $1 et ${name} insèrent ce qu'un groupe a capturé. Le chapitre groupes regex approfondit les groupes non capturants et les références arrière.
Pattern date = Pattern.compile("(?<year>\\d{4})-(?<month>\\d{2})-(?<day>\\d{2})");
Matcher m = date.matcher("2024-11-30");
if (m.matches()) {
System.out.println(m.group("year")); // 2024
System.out.println(m.group(3)); // 30 (third numbered group)
}
// Reformat using back-references
System.out.println("2024-11-30".replaceFirst(
"(\\d{4})-(\\d{2})-(\\d{2})", "$3/$2/$1")); // 30/11/2024Drapeaux : insensible à la casse, multiligne et plus
Passez les drapeaux comme second argument à Pattern.compile, combinés avec |. Les plus utilisés sont CASE_INSENSITIVE, MULTILINE (pour que ^/$ correspondent aux sauts de ligne), et DOTALL (pour que . corresponde aussi aux nouvelles lignes). Les mêmes drapeaux peuvent être définis en ligne avec (?i), (?m), (?s). Consultez le chapitre drapeaux regex pour la liste complète et leurs compromis.
Pattern p = Pattern.compile("error", Pattern.CASE_INSENSITIVE);
System.out.println(p.matcher("FATAL ERROR").find()); // true
// Equivalent inline form:
Pattern.compile("(?i)error");Un exemple concret : analyse d'une ligne de journal avec un pattern compilé
Ce programme compile un pattern de date une seule fois et fait travailler un Matcher en profondeur — en analysant chaque date d'une chaîne avec find(), en comparant find() avec matches(), en reformatant via des références arrière de groupe, en découpant sur des espaces, et en lisant des valeurs depuis des groupes nommés.
Ce qu'il faut retenir de l'exécution :
find()est un scanner avec mémoire. La bouclewhile (m.find())a localisé les deux dates — à l'index6et à l'index23— car chaque appel reprend là où la correspondance précédente s'est terminée. C'est ainsi qu'on énumère chaque occurrence, et c'est pourquoi le total était2.matches()est tout ou rien.matches whole log?a affichéfalsecar les dates sont enfouies dans un autre texte, tandis quematches one date?a affichétruecar"2024-01-15"est l'entrée entière. Utilisezfind()pour localiser,matches()pour valider.- Les groupes numérotés sont accessibles en cours de correspondance. Dans la boucle,
m.group(1)a retourné uniquement l'année à quatre chiffres (2024) tandis quem.group()a retourné la date entière — le groupe0est la correspondance, les groupes1..nsont les captures entre parenthèses de gauche à droite. - Les références arrière réarrangent le texte.
replaceAll("$3/$2/$1")a transformé chaqueYYYY-MM-DDenDD/MM/YYYY, produisantstart 15/01/2024 build 30/11/2024 done— le moteur a substitué chaque groupe capturé dans le modèle de remplacement. - Les groupes nommés se lisent comme des champs. Découper
"a b c"sur\s+a réduit les séquences d'espaces à3parties, et le pattern nommé a permis ànm.group("user")etnm.group("host")d'extrairealiceetw3docs.compar nom plutôt que par des numéros de position fragiles.