Перейти к основному содержимому

Незахватывающие группы регулярных выражений в Java

· 6 мин. чтения

1. Обзор

Незахватывающие группы являются важными конструкциями в регулярных выражениях Java . Они создают подшаблон, который функционирует как единое целое, но не сохраняет совпадающую последовательность символов. В этом руководстве мы рассмотрим, как использовать группы без захвата в регулярных выражениях Java.

2. Группы регулярных выражений

Группы регулярных выражений могут быть одного из двух типов: захватывающие и не захватывающие.

Группы захвата сохраняют совпадающую последовательность символов. Их значения могут использоваться в качестве обратных ссылок в шаблоне и/или извлекаться позже в коде.

Хотя они не сохраняют совпадающую последовательность символов, незахватывающие группы могут изменять модификаторы сопоставления шаблонов внутри группы. Некоторые группы без захвата могут даже отбрасывать информацию о возврате после успешного совпадения подшаблона.

Давайте рассмотрим несколько примеров групп без захвата в действии.

3. Группы без захвата

Группа без захвата создается с помощью оператора « (?:X) ». « X » — это шаблон для группы :

Pattern.compile("[^:]+://(?:[.a-z]+/?)+")

Этот шаблон имеет одну незахватывающую группу. Оно будет соответствовать значению, если оно похоже на URL. Полное регулярное выражение для URL было бы намного сложнее. Мы используем простой шаблон, чтобы сосредоточиться на незахватываемых группах.

Шаблон «[^:]: » соответствует протоколу — например, « http:// ». Незахватывающая группа « (?:[.az]+/?) » соответствует доменному имени с необязательным слэшем. Поскольку оператор « + » соответствует одному или нескольким вхождениям этого шаблона, мы также будем сопоставлять последующие сегменты пути. Давайте проверим этот шаблон на URL:

Pattern simpleUrlPattern = Pattern.compile("[^:]+://(?:[.a-z]+/?)+");
Matcher urlMatcher
= simpleUrlPattern.matcher("http://www.microsoft.com/some/other/url/path");

Assertions.assertThat(urlMatcher.matches()).isTrue();

Давайте посмотрим, что происходит, когда мы пытаемся получить совпадающий текст:

Pattern simpleUrlPattern = Pattern.compile("[^:]+://(?:[.a-z]+/?)+");
Matcher urlMatcher = simpleUrlPattern.matcher("http://www.microsoft.com/");

Assertions.assertThat(urlMatcher.matches()).isTrue();
Assertions.assertThatThrownBy(() -> urlMatcher.group(1))
.isInstanceOf(IndexOutOfBoundsException.class);

Регулярное выражение компилируется в объект java.util.Pattern . Затем мы создаем java.util.Matcher , чтобы применить наш шаблон к предоставленному значению.

Далее мы утверждаем, что результат match() возвращает true .

Мы использовали группу без захвата, чтобы соответствовать имени домена в URL-адресе. Поскольку группы без захвата не сохраняют совпадающий текст, мы не можем получить совпадающий текст «www.microsoft.com/» . Попытка получить доменное имя приведет к исключению IndexOutOfBoundsException .

3.1. Встроенные модификаторы

Регулярные выражения чувствительны к регистру. Если мы применим наш шаблон к URL-адресу смешанного регистра, совпадение не удастся:

Pattern simpleUrlPattern
= Pattern.compile("[^:]+://(?:[.a-z]+/?)+");
Matcher urlMatcher
= simpleUrlPattern.matcher("http://www.Microsoft.com/");

Assertions.assertThat(urlMatcher.matches()).isFalse();

В случае, когда мы хотим также сопоставить прописные буквы, есть несколько вариантов, которые мы могли бы попробовать.

Один из вариантов — добавить в шаблон диапазон символов верхнего регистра:

Pattern.compile("[^:]+://(?:[.a-zA-Z]+/?)+")

Другой вариант — использовать флаги-модификаторы. Итак, мы можем скомпилировать регулярное выражение без учета регистра:

Pattern.compile("[^:]+://(?:[.a-z]+/?)+", Pattern.CASE_INSENSITIVE)

Группы без захвата допускают третий вариант: мы можем изменить флаги модификатора только для группы. Давайте добавим в группу флаг модификатора без учета регистра (« i »):

Pattern.compile("[^:]+://(?i:[.a-z]+/?)+");

Теперь, когда мы сделали группу нечувствительной к регистру, давайте применим этот шаблон к URL-адресу со смешанным регистром:

Pattern scopedCaseInsensitiveUrlPattern
= Pattern.compile("[^:]+://(?i:[.a-z]+/?)+");
Matcher urlMatcher
= scopedCaseInsensitiveUrlPattern.matcher("http://www.Microsoft.com/");

Assertions.assertThat(urlMatcher.matches()).isTrue();

Когда шаблон скомпилирован без учета регистра, мы можем отключить его, добавив оператор «-» перед модификатором. Давайте применим этот шаблон к другому URL-адресу смешанного регистра:

Pattern scopedCaseSensitiveUrlPattern
= Pattern.compile("[^:]+://(?-i:[.a-z]+/?)+/ending-path", Pattern.CASE_INSENSITIVE);
Matcher urlMatcher
= scopedCaseSensitiveUrlPattern.matcher("http://www.Microsoft.com/ending-path");

Assertions.assertThat(urlMatcher.matches()).isFalse();

В этом примере последний сегмент пути « /ending-path » нечувствителен к регистру. Часть шаблона « /ending-path » будет соответствовать символам верхнего и нижнего регистра.

Когда мы отключили параметр без учета регистра внутри группы, группа без захвата поддерживала только символы нижнего регистра. Поэтому доменное имя в смешанном регистре не совпало.

4. Независимые незахватывающие группы

Независимые незахватывающие группы — это тип группы регулярных выражений. Эти группы отбрасывают информацию о возврате после нахождения успешного совпадения . При использовании этого типа группы мы должны знать, когда может произойти возврат. В противном случае наши шаблоны могут не соответствовать значениям, которые, по нашему мнению, должны быть.

Поиск с возвратом — это функция движков регулярных выражений недетерминированного конечного автомата (NFA). Когда обработчику не удается сопоставить текст, обработчик NFA может исследовать альтернативы в шаблоне. Двигатель провалит матч после исчерпания всех доступных альтернатив. Мы рассматриваем возврат только в том случае, если он относится к независимым незахватывающим группам.

Независимая незахватывающая группа создается с помощью оператора « (?>X) », где X — подшаблон:

Pattern.compile("[^:]+://(?>[.a-z]+/?)+/ending-path");

Мы добавили « /ending-path » в качестве постоянного сегмента пути. Наличие этого дополнительного требования вызывает ситуацию возврата. Имя домена и другие сегменты пути могут соответствовать символу косой черты. Чтобы соответствовать «/ending-path» , движку потребуется вернуться. Путем возврата движок может удалить косую черту из группы и применить ее к части шаблона « /ending-path ».

Давайте применим наш независимый шаблон группы без захвата к URL-адресу:

Pattern independentUrlPattern
= Pattern.compile("[^:]+://(?>[.a-z]+/?)+/ending-path");
Matcher independentMatcher
= independentUrlPattern.matcher("http://www.microsoft.com/ending-path");

Assertions.assertThat(independentMatcher.matches()).isFalse();

Группа успешно соответствует доменному имени и косой черте. Итак, мы выходим из области действия независимой незахватывающей группы.

Этот шаблон требует наличия косой черты перед «конечным путем» . Однако наша независимая группа без захвата соответствует косой черте.

Движок NFA должен попытаться откатиться назад. Поскольку косая черта не является обязательной в конце группы, механизм NFA удалит косую черту из группы и повторит попытку. Независимая незахватывающая группа отбросила информацию о возврате. Таким образом, двигатель NFA не может вернуться назад.

4.1. Откат внутри группы

Возврат может происходить внутри независимой группы без захвата. Пока механизм NFA сопоставляет группу, информация о возврате не отбрасывается. Информация о возврате не отбрасывается до тех пор, пока группа не сопоставляется успешно:

Pattern independentUrlPatternWithBacktracking
= Pattern.compile("[^:]+://(?>(?:[.a-z]+/?)+/)ending-path");
Matcher independentMatcher
= independentUrlPatternWithBacktracking.matcher("http://www.microsoft.com/ending-path");

Assertions.assertThat(independentMatcher.matches()).isTrue();

Теперь у нас есть группа без захвата внутри независимой группы без захвата. У нас все еще есть ситуация возврата, связанная с косой чертой перед «конечным путем» . Однако мы поместили часть шаблона с возвратом внутрь независимой незахватывающей группы. Возврат будет происходить внутри независимой группы без захвата. Таким образом, механизм NFA имеет достаточно информации для возврата, и шаблон соответствует указанному URL-адресу.

5. Вывод

Мы показали, что группы без захвата отличаются от групп с захватом. Однако они функционируют как единое целое, как и их коллеги-захватчики. Мы также показали, что группы без захвата могут включать или отключать модификаторы для группы, а не для шаблона в целом .

Точно так же мы показали, как независимые группы, не осуществляющие захват, отбрасывают информацию о возврате. Без этой информации механизм NFA не может исследовать альтернативы для успешного сопоставления. Однако внутри группы может произойти откат.

Как всегда, исходный код доступен на GitHub .