Группы и захваты в Java Regex
Захватывайте части совпадений в Java regex с помощью скобок, нумерованных и именованных групп.
Регулярное выражение не просто сообщает, совпадает ли строка — оно может разбить совпадение на части, которые можно считать. Эти части называются группами. Оборачивая часть шаблона в скобки, вы создаёте захватывающую группу, и после успешного выполнения Matcher вы можете извлечь каждую группу по номеру или по имени. В этой главе рассматриваются нумерованные группы, именованные группы, обратные ссылки, незахватывающие группы, а также их применение в заменах.
Нумерованные захватывающие группы
Каждая пара скобок в шаблоне открывает захватывающую группу, пронумерованную слева направо по открывающей (. Группа 0 является особой: она всегда соответствует всему совпадению. Таким образом, (\d{4})-(\d{2})-(\d{2}) даёт четыре группы — полную дату плюс год, месяц и день.
Pattern p = Pattern.compile("(\\d{4})-(\\d{2})-(\\d{2})");
Matcher m = p.matcher("2026-05-30");
if (m.matches()) {
System.out.println(m.group(0)); // 2026-05-30 (entire match)
System.out.println(m.group(1)); // 2026
System.out.println(m.group(2)); // 05
System.out.println(m.group(3)); // 30
}groupCount() возвращает количество захватывающих групп без учёта группы 0, поэтому приведённый выше шаблон возвращает 3. Обращение к несуществующему индексу группы приводит к IndexOutOfBoundsException.
Именованные группы
Подсчёт скобок становится ненадёжным по мере усложнения шаблонов. Именованные группы, записываемые как (?<name>...), позволяют читать захват по понятной метке, а не по индексу. Имена должны быть допустимыми Java-идентификаторами и уникальными в пределах шаблона.
Pattern p = Pattern.compile("(?<user>[\\w.]+)@(?<host>[\\w.]+)");
Matcher m = p.matcher("[email protected]");
if (m.matches()) {
System.out.println(m.group("user")); // ada
System.out.println(m.group("host")); // math.org
}Именованные группы по-прежнему нумеруются под капотом, поэтому m.group(1) и m.group("user") возвращают один и тот же текст. Имя существует исключительно для удобства чтения.
Обратные ссылки
Обратная ссылка соответствует тому же тексту, который уже захватила предыдущая группа. Внутри шаблона пишется \1 для группы 1 (или \k<name> для именованной группы). Так можно обнаружить повторение — например, удвоенное слово — в пределах одного совпадения.
// \b(\w+)\s+\1\b matches a word followed by the same word again
Pattern p = Pattern.compile("\\b(\\w+)\\s+\\1\\b");
Matcher m = p.matcher("the the end");
if (m.find()) {
System.out.println(m.group(1)); // the
}Обратите внимание на двойной обратный слеш в Java-коде: \\1 в строке превращается в \1 в фактическом регулярном выражении. Обратная ссылка может сработать только после того, как группа захватила текст, поэтому группа должна располагаться раньше в шаблоне.
Захватывающие группы в заменах
String.replaceAll, Matcher.replaceAll и appendReplacement — все они понимают ссылки на группы в тексте замены. Используйте $1, $2, ... для нумерованных групп и ${name} для именованных. Это превращает регулярные выражения в небольшой инструмент переупорядочивания и шаблонизации.
| Ссылка | Значение в замене |
|---|---|
$0 | Всё совпадение |
$1, $2, ... | Нумерованные захватывающие группы |
${name} | Именованная захватывающая группа |
\$ | Символ доллара в буквальном виде |
// Reorder "First Last" into "Last, First"
String out = "Ada Lovelace".replaceAll("(\\w+)\\s+(\\w+)", "$2, $1");
System.out.println(out); // Lovelace, AdaЕсли в вывод нужно подставить буквальный $ или \, экранируйте их как \\$ или \\\\ в Java-строке.
Незахватывающие группы
Иногда скобки нужны только для группировки альтернации или применения квантификатора — без захвата. Незахватывающая группа (?:...) делает именно это: группирует без потребления номера группы, что делает индексы чистыми, а работу движка чуть быстрее.
// Group the protocol alternation, but capture only the host
Pattern p = Pattern.compile("(?:https?|ftp)://(\\S+)");
Matcher m = p.matcher("https://w3docs.com");
if (m.find()) {
System.out.println(m.group(1)); // w3docs.com (group 1, not 2)
}Поскольку (?:https?|ftp) является незахватывающей группой, хост оказывается в группе 1, а не в группе 2. Необязательная захватывающая группа, не участвовавшая в совпадении, возвращает null, поэтому всегда проверяйте необязательные группы на null перед использованием.
Запускаемый пример
Программа ниже демонстрирует все виды групп за один запуск: нумерованные части даты, именованные поля email, обратную ссылку на удвоенное слово, ссылки на группы в двух заменах, незахватывающую группу протокола и необязательную группу, возвращающую null.
Что следует вынести из запуска:
- Две строки с датами показывают нумерованные группы:
group(0)— всё совпадение, аgroup(1..3)— год, месяц и день, захваченные по позиции. - Строка с email доказывает, что именованные группы читают тот же текст, что и индексы, а
groupCount=2считает только именованные захваты, никогда не включая группу 0. Doubled: theиDoubled: satполучены через обратную ссылку\1, соответствующую тому, что группа слова только что захватила — каждое повторяющееся слово находится независимо.Swapped: Lovelace, Ada, Turing, Alanпоказывает переупорядочивание каждой пары имён с помощью$2, $1, аMasked: card [4111] [2222]демонстрирует именованную ссылку${num}в шаблоне вывода.- Незахватывающая
(?:https?|ftp)оставляет хост в группе 1 (w3docs.com), а необязательная группа дроби выводитfrac=null, поскольку не участвовала в совпадении со строкой42.
Что изучить дальше
Группы опираются на остальные возможности движка регулярных выражений, поэтому полезно хорошо разбираться в смежных темах:
- Pattern и Matcher — классы, методы
group(),groupCount()иreplaceAll()которых используются здесь. - Квантификаторы Regex —
{4},+и?определяют, сколько захватывает каждая группа. - Классы символов Regex —
\d,\wи\S— это то, из чего состоит большинство групп. - Флаги Regex — флаги вроде
CASE_INSENSITIVEизменяют то, что совпадает с вашими группами.