Matcher: примеры (JAVA)
matcher(CharSequence input): MatcherОбщее описание
Класс java.util.regex.Matcher служит для поиска соответствий регулярному выражению в заданной последовательности символов. Создание происходит через Pattern.matcher(CharSequence). Matcher применяется при необходимости пошагового поиска совпадений, извлечения групп, получения позиций совпадения и выполнения сложных замен с контролем процесса замены.
Основные сценарии использования: проверка наличия совпадений в тексте, извлечение захваченных групп, замена найденных фрагментов с поддержкой обратных ссылок, поэтапный разбор текста.
Ключевые методы, их назначение и возвращаемые значения:
boolean matches()- проверяет, полностью ли вход удовлетворяет шаблону; возвращаетtrueилиfalse.boolean find()- ищет следующее совпадение, начиная с текущей позиции; возвращаетtrue, если найдено совпадение.boolean find(int start)- поиск следующего совпадения, начиная с указанной позиции.boolean lookingAt()- проверяет, совпадает ли начало входа с шаблоном (частичное совпадение от начала).String group()/String group(int)- возвращает текст всей группы или указанной захваченной группы; при отсутствии совпадения бросаетIllegalStateExceptionили возвращаетnullдля несуществующей группы.int start()/int end()- начальная и конечная позиции последнего совпадения.int groupCount()- число захватывающих групп в шаблоне, возвращает целое.Matcher reset()/Matcher reset(CharSequence)- сброс состояния поиска; возвращает сам объектMatcher.String replaceAll(String replacement)/String replaceFirst(String replacement)- выполняет замену и возвращает изменённую строку.appendReplacement(StringBuffer, String)иappendTail(StringBuffer)- низкоуровневый механизм поэтапной замены для сложной логики; возвращают и модифицируют буфер.Matcher usePattern(Pattern)- смена шаблона для существующего Matcher; возвращает Matcher.MatchResult toMatchResult()- возвращает неизменяемый результат последнего совпадения.void region(int start, int end)- ограничение области поиска; не возвращает значение.Matcher useTransparentBounds(boolean)иMatcher useAnchoringBounds(boolean)- управление поведением якорей и границ.static String quoteReplacement(String s)(в Pattern) - экранирование строки замены для безопасного использования обратных ссылок.
Описание аргументов: обычно это примитивы int, булевы флаги и объекты String, Pattern, CharSequence. Исключения: IllegalStateException при обращении к группам без найденного совпадения, IndexOutOfBoundsException при неверных границах region, PatternSyntaxException при некорректном шаблоне.
Короткие примеры
Примеры наглядно демонстрируют основные варианты: полное совпадение, поиск фрагментов, группы и простая замена.
1) Полная проверка текста на соответствие шаблону
import java.util.regex.*;
Pattern p = Pattern.compile("\\d{3}-\\d{2}-\\d{4}");
Matcher m = p.matcher("123-45-6789");
System.out.println(m.matches());
true
2) Поиск всех совпадений и вывод групп
Pattern p = Pattern.compile("(\\w+)@(\\w+\\.\\w+)");
Matcher m = p.matcher("alice@mail.com bob@site.org");
while (m.find()) {
System.out.println(m.group(0) + " -> user: " + m.group(1) + ", domain: " + m.group(2));
}
alice@mail.com -> user: alice, domain: mail.com bob@site.org -> user: bob, domain: site.org
3) Простая замена с обратной ссылкой
Pattern p = Pattern.compile("(\\w+), (\\w+)");
Matcher m = p.matcher("Doe, John; Smith, Anna");
String result = m.replaceAll("$2 $1");
System.out.println(result);
John Doe; Anna Smith
4) Поэтапная замена с appendReplacement/appendTail
Pattern p = Pattern.compile("(\\d+)");
Matcher m = p.matcher("Items: 12, 5, 300");
StringBuffer sb = new StringBuffer();
while (m.find()) {
int val = Integer.parseInt(m.group(1));
m.appendReplacement(sb, String.valueOf(val * 2));
}
m.appendTail(sb);
System.out.println(sb.toString());
Items: 24, 10, 600
Альтернативы внутри Java
Несколько вариантов работы с шаблонами и их краткие особенности:
String.matches(String regex)- простой способ проверить полное совпадение; использует полный матч, не подходит для поиска подстрок.Pattern.split(CharSequence)- разделение строки по шаблону; предпочтительно для разбора на части без явного перебора совпадений.- Класс
Scanner- удобен для построчного чтения и простых шаблонов при разборе ввода; менее гибкий для сложных регулярных выражений. - Библиотеки вроде Apache Commons Lang / Google Guava - предлагают утилиты для регулярных операций, но для детального контроля лучше использовать Matcher.
Выбор зависит от задачи: для полного совпадения и короткой проверки - String.matches, для итеративного извлечения групп и сложных замен - Matcher, для разделения - Pattern.split.
Похожие возможности в других языках
Краткие примеры и отличия от Java:
PHP - preg_match_all и preg_replace:
$text = 'alice@mail.com bob@site.org';
preg_match_all('/(\w+)@(\w+\.\w+)/', $text, $m);
print_r($m[0]);
Array
(
[0] => alice@mail.com
[1] => bob@site.org
)
Отличие: синтаксис шаблонов с разделителями и модификаторами; функции возвращают массивы, нет отдельного объекта-итератора сравнимого с Matcher.
JavaScript - RegExp и методы строки:
const re = /(\w+)@(\w+\.\w+)/g;
const text = 'alice@mail.com bob@site.org';
let m;
while ((m = re.exec(text)) !== null) {
console.log(m[0], m[1], m[2]);
}
alice@mail.com alice mail.com bob@site.org bob site.org
Отличие: RegExp объект хранит позицию в lastIndex; поведение флагов и обратных ссылок похоже.
Python - модуль re:
import re
text = '123-45-6789'
print(bool(re.fullmatch(r'\d{3}-\d{2}-\d{4}', text)))
for m in re.finditer(r'(\w+)@(\w+\.\w+)', 'a@b.com c@d.org'):
print(m.group(0), m.group(1), m.group(2))
True a@b.com a b.com c@d.org c d.org
Отличие: Python разделяет функции и объектные интерфейсы; поведение очень сходно, но синтаксис методов и обработка флагов отличается.
SQL - в разных СУБД доступны LIKE или REGEXP:
-- MySQL
SELECT * FROM users WHERE email REGEXP '[[:alnum:]]+@[[:alnum:]]+\.[[:alnum:]]+';
-- возвращает строки, удовлетворяющие регулярному выражению
Отличие: ограничения синтаксиса и производительность на больших наборах данных.
C# - System.Text.RegularExpressions.Regex:
using System.Text.RegularExpressions;
var m = Regex.Match("Doe, John", "(\\w+), (\\w+)");
Console.WriteLine(m.Success ? m.Groups[2].Value + " " + m.Groups[1].Value : "");
John Doe
Отличие: очень близкая модель к Java, но методы и объекты названы по-другому; есть дополнительные опции для быстрых сопоставлений.
Lua - встроенные функции string.match и gsub:
print(string.match("abc 123", "(%d+)"))
123
Отличие: упрощённый синтаксис шаблонов, не все regex-фичи поддерживаются.
Go - пакет regexp:
import "regexp"
re := regexp.MustCompile(`(\w+)@(\w+\.\w+)`)
for _, m := range re.FindAllStringSubmatch("a@b.com c@d.org", -1) {
fmt.Println(m[0], m[1], m[2])
}
a@b.com a b.com c@d.org c d.org
Отличие: Go использует синтаксис, близкий к POSIX/RE2, без поддержки некоторых сложных конструкций PCRE.
Kotlin - класс Regex поверх Java API:
val r = Regex("(\\w+)@(\\w+\\.\\w+)")
val matches = r.findAll("u@d.com v@t.org").map { it.groupValues }.toList()
println(matches)
[[u@d.com, u, d.com], [v@t.org, v, t.org]]
Отличие: Kotlin предоставляет более удобный API и расширения для работы с регулярными выражениями, но под капотом часто используется та же реализация Java.
Типичные ошибки
Распространённые проблемы и их примеры с результатом.
1) Использование matches() вместо find(), когда нужен поиск подстроки
Pattern p = Pattern.compile("test");
Matcher m = p.matcher("this is a test");
System.out.println(m.matches());
System.out.println(m.find());
false true
Пояснение: matches() требует полного совпадения всей строки с шаблоном.
2) Неправильная нумерация групп (индексация начинается с 1 для захватывающих групп)
Pattern p = Pattern.compile("(a)(b)");
Matcher m = p.matcher("ab");
m.find();
System.out.println(m.group(0));
System.out.println(m.group(1));
System.out.println(m.group(2));
// System.out.println(m.group(3)); // вызовет исключение
ab a b
3) Игнорирование экранирования в строковых литералах
Pattern p = Pattern.compile("\d+\\.\d+"); // задумана дробь, но слеши перепутаны
// Правильно: "\\d+\\.\\d+"
PatternSyntaxException или некорректный шаблон в рантайме
4) Неправильное использование appendReplacement без appendTail
Pattern p = Pattern.compile("(\\d+)");
Matcher m = p.matcher("v1 2 v3");
StringBuffer sb = new StringBuffer();
while (m.find()) {
m.appendReplacement(sb, "x");
}
// m.appendTail(sb); // если забыть, хвост строки не будет добавлен
System.out.println(sb.toString());
x (остаётся без хвоста, часть строки теряется)
5) Ошибки с region: неверные границы приводят к IndexOutOfBoundsException
Pattern p = Pattern.compile("a");
Matcher m = p.matcher("abc");
m.region(0, 10); // ошибка, т.к. конец выходит за длину
IndexOutOfBoundsException
Изменения и история
Класс Matcher и связанный Pattern введены в Java начиная с версии 1.4 и остаются основной реализацией регулярных выражений в JVM. В последующих релизах происходили в основном оптимизации производительности и исправления ошибок. Поддержка именованных групп доступна в современных версиях JVM и широко используется в коде.
Небольшие расширения и улучшения в API по большей части связаны с удобством использования в языках, работающих поверх JVM (например, Kotlin), а также с повышением устойчивости к ошибкам и исправлениями регресcий в реализации движка регулярных выражений.
Расширенные примеры
Несколько более сложных приёмов, где класс Matcher предоставляет мощный контроль над процессом.
1) Именованные группы и обращение к ним
Pattern p = Pattern.compile("(?\\w+)@(?\\w+\\.\\w+)");
Matcher m = p.matcher("alice@mail.com");
if (m.find()) {
System.out.println(m.group("name") + " -> " + m.group("domain"));
}
alice -> mail.com
2) Преобразование найденных чисел функционально с помощью appendReplacement
Pattern p = Pattern.compile("(\\d+)");
Matcher m = p.matcher("values: 10, 200, 3");
StringBuffer sb = new StringBuffer();
while (m.find()) {
int n = Integer.parseInt(m.group(1));
// форматирование: числа с ведущими нулями до 4 знаков
m.appendReplacement(sb, String.format("%04d", n));
}
m.appendTail(sb);
System.out.println(sb.toString());
values: 0010, 0200, 0003
3) Ограничение области поиска через region для парсинга сегментов
Pattern p = Pattern.compile("\\w+");
Matcher m = p.matcher("first second third");
// искать только во втором слове
m.region(6, 12);
if (m.find()) System.out.println(m.group());
second
4) Lookahead/Lookbehind для поиска без включения в результат
Pattern p = Pattern.compile("\\w+(?=:)\:");
Matcher m = p.matcher("key1:value1 key2:value2");
while (m.find()) System.out.println(m.group());
(зависит от шаблона; пример иллюстративен)
5) Сохранение неизменяемого результата для многопоточной проверки
Pattern p = Pattern.compile("(\\w+)@(\\w+\\.\\w+)");
Matcher m = p.matcher("u@d.com");
if (m.find()) {
MatchResult mr = m.toMatchResult();
// mr можно безопасно передать в другой поток для чтения результата
System.out.println(mr.group(1) + "@" + mr.group(2));
}
u@d.com
6) Использование флагов для многострочного и dotall поведения
Pattern p = Pattern.compile("^start.*end$", Pattern.DOTALL | Pattern.MULTILINE);
Matcher m = p.matcher("start\nmiddle\nend");
System.out.println(m.find());
true
7) Преобразование результата с учётом экранирования замены
String user = "$admin";
String replacement = Pattern.quoteReplacement(user);
Pattern p = Pattern.compile("\$\w+");
Matcher m = p.matcher("Hello $admin and $root");
System.out.println(m.replaceAll(replacement));
Hello $admin and $admin
Каждый пример иллюстрирует практические приёмы: именованные группы для читаемости, appendReplacement для нестандартных преобразований, region для локализованного поиска, флаги для управления поведением шаблона и безопасное экранирование строк замены.